树莓派的单板机为廉价、实际的计算设定了标准。凭借其 Model 4,树莓派可以托管 Web 应用程序,并配备生产级 Web 服务器、事务数据库系统以及通过脚本实现的动态内容。本文通过完整的代码示例解释了安装和配置的详细信息。欢迎来到在非常轻量级的计算机上托管的 Web 应用程序。
降雪量应用程序
想象一个足够大的下坡滑雪区,拥有微气候,这意味着整个区域的降雪量可能差异很大。该区域划分为多个区域,每个区域都有记录厘米降雪量的设备;记录的信息然后指导有关造雪、压雪和其他维护操作的决策。这些设备例如每 20 分钟与服务器通信一次,服务器更新数据库以支持报告。如今,此类应用程序的服务器端软件可以是免费的并且是生产级的。
此降雪量应用程序使用以下技术
- 运行 Debian 的 树莓派 4
- Nginx Web 服务器:免费版本托管超过 4 亿个网站。此 Web 服务器易于安装、配置和使用。
- SQLite 关系数据库系统,它是基于文件的:数据库(可以容纳许多表)是本地系统上的一个文件。SQLite 很轻量级,但也符合 ACID 标准;它适用于低到中等容量。SQLite 可能是世界上使用最广泛的数据库系统,并且 SQLite 的源代码位于公共领域。当前版本是 3。更强大(但仍然免费)的选择是 PostgreSQL。
- Python:Python 编程语言可以与 SQLite 等数据库和 Nginx 等 Web 服务器交互。Python(版本 3)随 Linux 和 macOS 系统一起提供。
Python 包括一个用于与 SQLite 通信的软件驱动程序。有多种选项可以将 Python 脚本与 Nginx 和其他 Web 服务器连接。一种选择是 uWSGI(Web 服务器网关接口),它更新了 20 世纪 90 年代古老的 CGI(通用网关接口)。
有几个因素支持 uWSGI
- uWSGI 非常灵活。它可以用作轻量级并发 Web 服务器,也可以用作连接到 Nginx 等 Web 服务器的后端应用程序服务器。
- 它的设置非常简单。
- 降雪量应用程序涉及对 Web 服务器和数据库系统的低到中等容量的访问。一般来说,按照现代标准,CGI 技术速度不快,但 CGI 对于部门级 Web 应用程序(例如这个)来说性能足够好。
各种首字母缩略词描述了 uWSGI 选项。以下是三个主要选项的草图
- WSGI 是 Python 规范,用于 Web 服务器(在一侧)与应用程序或应用程序框架(例如 Django)(在另一侧)之间的接口。此规范定义了一个 API,其实现是开放的。
- uWSGI 通过提供应用程序服务器来实现 WSGI 接口,该服务器将应用程序连接到 Web 服务器。uWSGI 应用程序服务器的主要工作是将 HTTP 请求转换为 Web 应用程序可以使用的格式,然后在之后将应用程序的响应格式化为 HTTP 消息。
- uwsgi 是 uWSGI 应用程序服务器实现的二进制协议,用于与功能齐全的 Web 服务器(例如 Nginx)通信;它还包括轻量级 Web 服务器等实用程序。Nginx Web 服务器“开箱即用”地“说”uwsgi。
为方便起见,我将使用“uwsgi”作为二进制协议、应用程序服务器和非常轻量级的 Web 服务器的简写。
设置数据库
在基于 Debian 的系统上,您可以像往常一样安装 SQLite(使用 %
表示命令行提示符)
% sudo apt-get install sqlite3
此数据库系统是 C 库和实用程序的集合,所有这些库和实用程序的大小约为 500KB。没有数据库服务器需要启动、停止或以其他方式维护。
安装 SQLite 后,在命令行提示符下创建一个数据库
% sqlite3 snowfall.db
如果成功,该命令将在当前工作目录中创建文件 snowfall.db
。数据库名称是任意的(例如,不需要扩展名),并且该命令使用 >sqlite
作为提示符打开 SQLite 客户端实用程序
Enter ".help" for usage hints.
sqlite>
使用以下命令在 snowfall 数据库中创建 snowfall 表。表名(如数据库名称)是任意的
sqlite> CREATE TABLE snowfall (id INTEGER PRIMARY KEY AUTOINCREMENT,
region TEXT NOT NULL,
device TEXT NOT NULL,
amount DECIMAL NOT NULL,
tstamp DECIMAL NOT NULL);
SQLite 命令不区分大小写,但传统上对 SQL 术语使用大写,对用户术语使用小写。检查表是否已创建
sqlite> .schema
该命令会回显 CREATE TABLE
语句。
数据库现在已准备就绪,可以投入使用,尽管单表 snowfall 是空的。您可以交互式地向表中添加行,但现在空表就可以了。
首次了解总体架构
回想一下,uwsgi 可以通过两种方式使用:既可以用作轻量级 Web 服务器,也可以用作连接到生产级 Web 服务器(例如 Nginx)的应用程序服务器。第二个用途是目标,但第一个用途适用于开发和测试程序员的请求处理代码。以下是以 Nginx 作为 Web 服务器的架构
HTTP uwsgi
client<---->Nginx<----->appServer<--->request-handling code<--->SQLite
客户端可以是浏览器、curl 等实用程序或精通 HTTP 的手工程序。客户端和 Nginx 之间的通信通过 HTTP 进行,但随后 uwsgi 接管作为 Nginx 和应用程序服务器之间的二进制传输协议,该应用程序服务器与请求处理代码(例如 requestHandler.py
(如下所述))交互。此架构提供了清晰的职责分工。Nginx 单独管理客户端,只有请求处理代码与数据库交互。反过来,应用程序服务器将 Web 服务器与程序员编写的代码分开,后者具有高级 API 来读取和写入通过 uwsgi 传递的 HTTP 消息。
我将在接下来的部分中检查这些架构组件,并介绍安装、配置和使用 uwsgi 和 Nginx 的步骤。
降雪量应用程序代码
以下是降雪量应用程序的源代码文件 requestHandler.py
。(它也可以在我的 网站上找到。)此代码中的不同函数有助于阐明连接 SQLite、Nginx 和 uwsgi 的软件架构。
请求处理程序
import sqlite3
import cgi
PATH_2_DB = '/home/marty/wsgi/snowfall.db'
## Dispatches HTTP requests to the appropriate handler.
def application(env, start_line):
if env['REQUEST_METHOD'] == 'POST': ## add new DB record
return handle_post(env, start_line)
elif env['REQUEST_METHOD'] == 'GET': ## create HTML-fragment report
return handle_get(start_line)
else: ## no other option for now
start_line('405 METHOD NOT ALLOWED', [('Content-Type', 'text/plain')])
response_body = 'Only POST and GET verbs supported.'
return [response_body.encode()]
def handle_post(env, start_line):
form = get_field_storage(env) ## body of an HTTP POST request
## Extract fields from POST form.
region = form.getvalue('region')
device = form.getvalue('device')
amount = form.getvalue('amount')
tstamp = form.getvalue('tstamp')
## Missing info?
if (region is not None and
device is not None and
amount is not None and
tstamp is not None):
add_record(region, device, amount, tstamp)
response_body = "POST request handled.\n"
start_line('201 OK', [('Content-Type', 'text/plain')])
else:
response_body = "Missing info in POST request.\n"
start_line('400 Bad Request', [('Content-Type', 'text/plain')])
return [response_body.encode()]
def handle_get(start_line):
conn = sqlite3.connect(PATH_2_DB) ## connect to DB
cursor = conn.cursor() ## get a cursor
cursor.execute("select * from snowfall")
response_body = "<h3>Snowfall report</h3><ul>"
rows = cursor.fetchall()
for row in rows:
response_body += "<li>" + str(row[0]) + '|' ## primary key
response_body += row[1] + '|' ## region
response_body += row[2] + '|' ## device
response_body += str(row[3]) + '|' ## amount
response_body += str(row[4]) + "</li>" ## timestamp
response_body += "</ul>"
conn.commit() ## commit
conn.close() ## cleanup
start_line('200 OK', [('Content-Type', 'text/html')])
return [response_body.encode()]
## Add a record from a device to the DB.
def add_record(reg, dev, amt, tstamp):
conn = sqlite3.connect(PATH_2_DB) ## connect to DB
cursor = conn.cursor() ## get a cursor
sql = "INSERT INTO snowfall(region,device,amount,tstamp) values (?,?,?,?)"
cursor.execute(sql, (reg, dev, amt, tstamp)) ## execute INSERT
conn.commit() ## commit
conn.close() ## cleanup
def get_field_storage(env):
input = env['wsgi.input']
form = env.get('wsgi.post_form')
if (form is not None and form[0] is input):
return form[2]
fs = cgi.FieldStorage(fp = input,
environ = env,
keep_blank_values = 1)
return fs
源文件开头的常量定义了数据库文件的路径
PATH_2_DB = '/home/marty/wsgi/snowfall.db'
请务必更新您的树莓派的路径。
如前所述,uwsgi 包括一个可以托管此请求处理应用程序的轻量级 Web 服务器。首先,使用以下两个命令安装 uwsgi(##
引入我的注释)
% sudo apt-get install build-essential python-dev ## C header files, etc.
% pip install uwsgi ## pip = Python package manager
接下来,使用 uwsgi 作为 Web 服务器启动一个最基本的降雪量应用程序
% uwsgi --http 127.0.0.1:9999 --wsgi-file requestHandler.py
标志 --http
在 Web 服务器模式下运行 uwsgi,其中 9999 是 Web 服务器在 localhost (127.0.0.1) 上的侦听端口。默认情况下,uwsgi 将 HTTP 请求分派给程序员定义的名为 application
的函数。回顾一下,以下是 requestHandler.py
代码顶部的完整函数
def application(env, start_line):
if env['REQUEST_METHOD'] == 'POST': ## add new DB record
return handle_post(env, start_line)
elif env['REQUEST_METHOD'] == 'GET': ## create HTML-fragment report
return handle_get(start_line)
else: ## no other option for now
start_line('405 METHOD NOT ALLOWED', [('Content-Type', 'text/plain')])
response_body = 'Only POST and GET verbs supported.'
return [response_body.encode()]
降雪量应用程序仅接受两种请求类型
- POST 请求,如果符合标准,则在 snowfall 表中创建一个新条目。该请求应包括滑雪区区域、区域中的设备、厘米降雪量和 Unix 样式的 时间戳。POST 请求被分派到
handle_post
函数(我稍后将澄清)。 - GET 请求返回一个 HTML 片段(无序列表),其中包含 snowfall 表中当前记录。
HTTP 动词不是 POST 和 GET 的请求将生成错误消息。
您可以使用 curl 等实用程序生成 HTTP 请求以进行测试。以下是三个示例 POST 请求,用于开始填充数据库
% curl -X POST -d "region=R1&device=D9&amount=1.42&tstamp=1604722088.0158753" localhost:9999/
% curl -X POST -d "region=R7&device=D4&amount=2.11&tstamp=1604722296.8862638" localhost:9999/
% curl -X POST -d "region=R5&device=D1&amount=1.12&tstamp=1604942236.1013834" localhost:9999/
这些命令向 snowfall 表添加了三条记录。来自 curl 或浏览器的后续 GET 请求会显示一个 HTML 片段,其中列出了 snowfall 表中的行。以下是等效的非 HTML 文本
Snowfall report
1|R1|D9|1.42|1604722088.0158753
2|R7|D4|2.11|1604722296.8862638
3|R5|D1|1.12|1604942236.1013834
专业的报告会将数字时间戳转换为人类可读的时间戳。但目前的重点是降雪量应用程序中的架构组件,而不是用户界面。
uwsgi 实用程序接受各种标志,这些标志可以通过配置文件或启动命令给出。例如,以下是作为 Web 服务器的 uwsgi 的更丰富的启动
% uwsgi --master --processes 2 --http 127.0.0.1:9999 --wsgi-file requestHandler.py
此版本创建一个主(监控)进程和两个工作进程,它们可以并发处理 HTTP 请求。
在降雪量应用程序中,函数 handle_post
和 handle_get
分别处理 POST 和 GET 请求。以下是完整的 handle_post
函数
def handle_post(env, start_line):
form = get_field_storage(env) ## body of an HTTP POST request
## Extract fields from POST form.
region = form.getvalue('region')
device = form.getvalue('device')
amount = form.getvalue('amount')
tstamp = form.getvalue('tstamp')
## Missing info?
if (region is not None and
device is not None and
amount is not None and
tstamp is not None):
add_record(region, device, amount, tstamp)
response_body = "POST request handled.\n"
start_line('201 OK', [('Content-Type', 'text/plain')])
else:
response_body = "Missing info in POST request.\n"
start_line('400 Bad Request', [('Content-Type', 'text/plain')])
return [response_body.encode()]
handle_post
函数的两个参数(env
和 start_line
)分别表示系统环境和通信通道。start_line
通道发送 HTTP 启动行(在本例中为 400 Bad Request
或 201 OK
)和 HTTP 响应的任何 HTTP 标头(在本例中仅为 Content-Type: text/plain
)。
handle_post
函数尝试从 HTTP POST 请求中提取相关数据,如果成功,则调用函数 add_record
以向 snowfall 表添加另一行
def add_record(reg, dev, amt, tstamp):
conn = sqlite3.connect(PATH_2_DB) ## connect to DB
cursor = conn.cursor() ## get a cursor
sql = "INSERT INTO snowfall(region,device,amount,tstamp) VALUES (?,?,?,?)"
cursor.execute(sql, (reg, dev, amt, tstamp)) ## execute INSERT
conn.commit() ## commit
conn.close() ## cleanup
SQLite 自动将单个 SQL 语句(例如上面的 INSERT
)包装在事务中,这解释了代码中对 conn.commit()
的调用。SQLite 还支持多语句事务。调用 add_record
后,handle_post
函数通过向请求者发送 HTTP 响应确认消息来完成其工作。
handle_get
函数也访问数据库,但仅读取 snowfall 表中的记录
def handle_get(start_line):
conn = sqlite3.connect(PATH_2_DB) ## connect to DB
cursor = conn.cursor() ## get a cursor
cursor.execute("SELECT * FROM snowfall")
response_body = "<h3>Snowfall report</h3><ul>"
rows = cursor.fetchall()
for row in rows:
response_body += "<li>" + str(row[0]) + '|' ## primary key
response_body += row[1] + '|' ## region
response_body += row[2] + '|' ## device
response_body += str(row[3]) + '|' ## amount
response_body += str(row[4]) + "</li>" ## timestamp
response_body += "</ul>"
conn.commit() ## commit
conn.close() ## cleanup
start_line('200 OK', [('Content-Type', 'text/html')])
return [response_body.encode()]
用户友好的降雪量应用程序将支持其他(和更高级的)报告,但即使是这个版本的 handle_get
也强调了 Python 和 SQLite 之间的清晰接口。顺便说一句,uwsgi 期望响应正文是字节列表。在 return
语句中,对方括号内的 response_body.encode()
的调用从 response_body
字符串生成字节列表。
升级到 Nginx
可以使用一个命令在基于 Debian 的系统上安装 Nginx Web 服务器
% sudo apt-get install nginx
作为 Web 服务器,Nginx 提供预期的服务,例如线级安全性、HTTPS、用户身份验证、负载平衡、媒体流、响应压缩、文件上传等。Nginx 引擎具有高性能和稳定性,并且此服务器可以通过各种编程语言支持动态内容。使用 uwsgi 作为非常轻量级的 Web 服务器是一个有吸引力的选择,但切换到 Nginx 是升级到具有大容量能力的工业强度 Web 托管。Nginx 和 uwsgi 都是用 C 实现的。
在 Nginx 的作用下,uwsgi 承担了通信协议的受限角色和应用程序服务器;它不再充当 HTTP Web 服务器。以下是修改后的架构
HTTP uwsgi
requester<---->Nginx<----->app server<--->requestHandler.py
如前所述,Nginx 包括 uwsgi 支持,现在充当反向代理服务器,将指定的 HTTP 请求转发到 uwsgi 应用程序服务器,后者又与 Python 脚本 requestHandler.py
交互。来自 Python 脚本的响应以相反方向移动,以便 Nginx 将 HTTP 响应发送回请求客户端。
两个更改使这种新架构成为现实。第一个是将 uwsgi 作为应用程序服务器启动
% uwsgi --socket 127.0.0.1:8001 --wsgi-file requestHandler.py
套接字 8001 是 Nginx 用于 uwsgi 通信的默认值。为了提高稳健性,您可以使用 Python 脚本的完整路径,以便不必在包含 Python 脚本的目录中执行上述命令。在生产环境中,uwsgi 将自动启动和停止;但是,目前重点仍然是架构组件如何组合在一起。
第二个更改涉及 Nginx 配置,这在基于 Debian 的系统上可能很棘手。Nginx 的主配置文件是 /etc/nginx/nginx.conf
,但此文件可能具有针对其他文件的 include
指令,特别是三个 /etc/nginx
子目录之一中的文件:nginx.d
、sites-available
和 sites-enabled
。可以消除 include
指令以简化问题;在这种情况下,配置仅发生在 nginx.conf
中。我推荐简单的方法。
无论配置如何分布,让 Nginx 与 uwsgi 应用程序服务器对话的关键部分都以 http
开头,并且有一个或多个 server
子节,这些子节又具有 location
子节。以下是 Nginx 文档中的示例
...
http {
# Configuration specific to HTTP and affecting all virtual servers
...
server { # simple reverse-proxy
listen 80;
server_name domain2.com www.domain2.com;
access_log logs/domain2.access.log main;
# serve static files
location ~ ^/(images|javascript|js|css|flash|media|static)/ {
root /var/www/virtual/big.server.com/htdocs;
expires 30d;
}
# pass requests for dynamic content to rails/turbogears/zope, et al
location / {
proxy_pass http://127.0.0.1:8080;
}
}
...
}
location
子节是感兴趣的子节。对于降雪量应用程序,以下是添加的 location
条目及其两条配置行
...
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/html;
index index.html index.htm index.nginx-debian.html;
server_name _;
### key addition for uwsgi communication
location /snowfall {
include uwsgi_params; ## comes with Nginx
uwsgi_pass 127.0.0.1:8001; ## 8001 is the default for uwsgi
}
...
}
...
为了现在保持简单,将 /snowfall
作为配置中唯一的 location
。在此配置到位后,Nginx 侦听端口 80 并将以 /snowfall
路径结尾的 HTTP 请求分派到 uwsgi 应用程序服务器
% curl -X POST -d "..." localhost/snowfall ## new POST
% curl -X GET localhost/snowfall ## new GET
可以从请求中删除端口号 80,因为 80 是 HTTP 请求的默认服务器端口。
如果配置的位置只是 /
而不是 /snowfall
,则任何以 /
开头的 HTTP 请求都将分派到 uwsgi 应用程序服务器。因此,/snowfall
路径为其他位置留出了空间,因此也为响应 HTTP 请求的进一步操作留出了空间。
使用添加的 location
子节更改 Nginx 配置后,您可以启动 Web 服务器
% sudo systemctl start nginx
还有其他类似于 stop
和 restart
Nginx 的命令。在生产环境中,您可以自动化这些操作,以便 Nginx 在系统启动时启动,并在系统关闭时停止。
在 uwsgi 和 Nginx 都运行的情况下,您可以使用浏览器测试架构组件是否按预期协同工作。例如,如果您在浏览器的输入窗口中输入 URL localhost/
,则应出现 Nginx 欢迎页面,其(HTML)内容类似于以下内容
Welcome to nginx!
...
Thank you for using nginx.
相比之下,URL localhost/snowfall
应显示 snowfall 表中当前的行
Snowfall report
1|R1|D9|1.42|1604722088.0158753
2|R7|D4|2.11|1604722296.8862638
3|R5|D1|1.12|1604942236.1013834
总结
降雪量应用程序展示了免费软件组件——高性能 Web 服务器、符合 ACID 标准的数据库系统以及用于动态内容的脚本——如何在树莓派 4 平台上支持真实的 Web 应用程序。这款轻量级机器超出了其重量级别,而 Debian 简化了提升过程。
Web 应用程序中的软件组件协同工作良好,并且只需要很少的配置。对于针对关系数据库的更高容量的访问,请记住,SQLite 的免费且功能丰富的替代方案是 PostgreSQL。如果您渴望在树莓派 4 上玩耍——特别是探索此平台上的服务器端 Web 编程——那么值得考虑 Nginx、SQLite 或 PostgreSQL、uwsgi 和 Python。
1 条评论