Microdot一个可以在micropytho中搭建物联网web服务器的框架
只要是支持micropython的设备都可以使用它
什么是Microdot?Microdot是一个受Flask启发的简约Python Web框架,旨在运行在 资源有限的系统,例如微控制器(esp32,esp8266)。它在标准上运行 Python和MicroPython。
安装
对于标准的Python(CPython)项目,Microdot及其所有核心扩展 可安装:pip
pip install microdot
对于MicroPython,如果该选项可用,您可以使用它安装它, 但推荐的方法是手动复制 microdot.py 和任何 所需的可选扩展名源文件从 GitHub 存储库到您的设备中,可能在将它们编译为 .mpy 文件之后。这些源文件也可以冻结并合并到自定义MicroPython固件中。upip
开始
本节以非正式的方式介绍Microdot的主要功能。为 详细的参考信息,请参阅 API 参考。
一个简单的Microdot网络服务器
以下是简单 Web 服务器的示例:
from microdot import Microdot
app = Microdot()
@app.route('/')
def index(request):
return 'Hello, world!'
app.run(host='0.0.0.0', port=5000, debug=False, ssl=None)
脚本导入类并创建 来自它的应用程序实例。
应用程序实例提供了一个route装饰器,用于定义一个或多个路由,根据需要 应用。
route()装饰器将 URL 的路径部分作为 参数,并将其映射到修饰函数,以便调用该函数 当客户端请求 URL 时。该函数将对象作为参数传递,该参数提供 访问客户端传递的信息。返回的值由 函数作为响应发送回客户端。
run()该方法启动应用程序的 Web 端口 5000 上的服务器(或在参数中传递的端口号)。这 方法在等待来自客户端的连接时阻止。
与CPython一起跑步
| 必需的Microdot源文件 | microdot.py |
|---|---|
| 所需的外部依赖项 | 没有 |
| 例子 | hello.py |
使用 CPython 时,您可以通过运行脚本来启动 Web 服务器 定义并运行应用程序实例:
python main.py
在脚本运行时,您可以打开 Web 浏览器并导航到 http://localhost:5000/ ,这是 Microdot Web 的默认地址 服务器。从同一网络中的其他计算机,使用 IP 地址或 运行脚本的计算机的主机名,而不是 。localhost
使用 MicroPython 运行
| 必需的Microdot源文件 | microdot.py |
|---|---|
| 所需的外部依赖项 | 没有 |
| 例子 | hello.pygpio.py |
使用MicroPython时,您可以上传包含Web的 main.py 文件 服务器代码与 microdot.py 一起发送到您的设备。MicroPython 将 设备通电时自动运行 main.py,因此Web服务器 将自动启动。可以在端口 5000 上访问该应用程序,网址为 设备的 IP 地址。如上所述,可以通过将参数传递给方法来更改端口。port``run()
注意
Microdot 不配置其所在设备的网络接口 正在运行。如果您的设备需要建立网络连接 前进,例如到 Wi-Fi 接入点,这必须在之前进行配置 调用该方法。run()
定义多个路由
from microdot import Microdot
app = Microdot()
@app.route('/')
def index(request):
return 'Hello, world!'
@app.route('/users/active')
def index(request):
return 'test'
app.run(host='0.0.0.0', port=5000, debug=False, ssl=None)
向route中传入不容的路径可以声明不同的请求
设置不同的http请求方法
上面的实例都是默认get请求
如果要使用restful请求风格,可以给route传递不同的参数设置不同的请求
@app.route('/invoices', methods=['GET', 'POST'])
def invoices(request):
# 根据不同的请求做回应
if request.method == 'GET':
return 'get invoices'
elif request.method == 'POST':
return 'create an invoice'
这里给/invoices请求设置了get和post方法,当客服端发送get和post请求的时候,就会进入该路由中。
@app.route('/invoices', methods=['GET'])
def get_invoices(request):
return 'get invoices'
@app.route('/invoices', methods=['POST'])
def create_invoice(request):
return 'create an invoice'
也可以分开设置
microdot同时设置了 快捷函数
@app.get('/invoices')
def get_invoices(request):
return 'get invoices'
@app.post('/invoices')
def create_invoice(request):
return 'create an invoice'
put,delete请求同上
动态路由
上面的路由都定死了,有时候我们需要定义动态路由
@app.get('/users/<username>')
def get_user(request, username):
return 'User: ' + username
如示例中所示,括在尖括号中的路径组件 被认为是动态的。Microdot接受 URL 该部分的任何值 路径,并将收到的值作为参数传递给函数之后的参数 请求对象。
路由不限于单个动态组件。以下路线显示 如何在路径中包含多个动态组件:
@app.get('/users/<firstname>/<lastname>')
def get_user(request, firstname, lastname):
return 'User: ' + firstname + ' ' + lastname
默认情况下,动态路径组件被视为字符串。一个明确的 类型可以指定为前缀,与动态组件名称分隔 一个冒号。以下路由有两个声明为整数的动态组件 和字符串分别:
@app.get('/users/<int:id>/<string:username>')
def get_user(request, id, username):
return 'User: ' + username + ' (' + str(id) + ')'
如果将动态路径组件定义为整数,则传递给 路由函数也是一个整数。如果客户端发送的值不是 URL路径相应部分中的整数,则URL将不会 匹配,则不会调用路由。
可以使用特殊类型将路径的其余部分捕获为 单个参数:path
@app.get('/tests/<path:path>')
def get_test(request, path):
return 'Test: ' + path
对于大多数控件,该类型允许应用程序提供 动态组件的自定义正则表达式。下一个示例定义 仅匹配以大写或小写开头的用户名的路由 字母,后跟一系列字母或数字:
python复制代码@app.get('/users/<re:[a-zA-Z][a-zA-Z0-9]*:username>')
def get_user(request, username):
return 'User: ' + username
注意
动态路径组件作为关键字参数传递给路由函数, 因此,函数参数的名称必须与 路径规范。
路由前置程序
@app.before_request
def authenticate(request):
user = authorize(request)
if not user:
return 'Unauthorized', 401
request.g.user = user
在每一个路由请求前调用,可以校验用户身份等
路由后置程序
@app.after_request
def end_timer(request, response):
duration = time.time() - request.g.start_time
print(f'Request took {duration:0.2f} seconds')
在每一个路由请求后调用,
@app.before_request
def start_timer(request):
request.g.start_time = time.time()
@app.after_request
def end_timer(request, response):
duration = time.time() - request.g.start_time
print(f'Request took {duration:0.2f} seconds')
上面实现了一个记录请求响应时间长短的功能
另一种用法
@app.post('/logout')
def logout(request):
@request.after_request
def reset_session(request, response):
response.set_cookie('session', '', http_only=True)
return response
return 'Logged out'
错误/异常拦截
当在处理请求期间发生错误时,Microdot 确保 客户端收到相应的错误响应。一些常见错误 Microdot 自动处理的是:
- 400 表示格式错误的请求。
- 404 表示未定义的 URL。
- 405 表示已定义的 URL,但不用于请求的 HTTP 方法。
- 413 表示大于允许大小的请求。
- 当应用程序引发异常时为 500。
拦截404
@app.errorhandler(404)
def not_found(request):
return {'error': 'resource not found'}, 404
异常拦截
@app.errorhandler(ZeroDivisionError)
def division_by_zero(request, exception):
return {'error': 'division by zero'}, 500
拆分路由 挂载子应用程序
rodot应用程序可以写入单个源文件,但这 对于超过特定大小的应用程序,不是最佳选择。要做到 更简单的编写大型应用程序,Microdot 支持 可以“装载”到较大应用程序上的子应用程序,可能使用 应用于其所有路由的通用 URL 前缀。
例如,考虑一个 customers.py 实现 对客户的操作:
python复制代码from microdot import Microdot
customers_app = Microdot()
@customers_app.get('/')
def get_customers(request):
# return all customers
@customers_app.post('/')
def new_customer(request):
# create a new customer
以同样的方式,orders.py 子应用程序在 客户订单:
from microdot import Microdot
orders_app = Microdot()
@orders_app.get('/')
def get_orders(request):
# return all orders
@orders_app.post('/')
def new_order(request):
# create a new order
现在存储在 main.py 中的主应用程序可以导入和挂载 用于构建组合应用程序的子应用程序:
from microdot import Microdot
from customers import customers_app
from orders import orders_app
def create_app():
app = Microdot()
app.mount(customers_app, url_prefix='/customers')
app.mount(orders_app, url_prefix='/orders')
return app
app = create_app()
app.run()
生成的应用程序将具有在 /customers/ 上可用的客户终结点和在 /orders/ 上可用的订单终结点。
注意
在请求之前、请求之后和错误处理程序中定义的 子应用程序也会在挂载时复制到主应用程序。 一旦安装在主应用程序中,这些处理程序将应用于 整个应用程序,而不仅仅是它们所在的子应用程序 创建。
关闭服务器
Web 服务器设计为永久运行,并且经常通过发送它们来停止 中断信号。但是有一种优雅地停止服务器的方法 有时很有用,尤其是在测试环境中。Microdot提供了一种可以调用``的方法 在处理路由期间,当 请求完成。下一个示例演示如何使用此功能:
@app.get('/shutdown')
def shutdown(request):
request.app.shutdown()
return '服务器已经关闭...'
请求对象Request
Request的属性
- method: 请求的 HTTP 方法.
- path: 请求的路径.
- args:请求的查询字符串参数,类型是 MultiDict对象。
- headers:请求头
- cookies:cookies
- content_type:指定返回的内容类型
- content_length:内容的长度
- client_addr:获取网络地址
- app:请求对象实例
JSON
客户端发送JOSON内容,可以通过request.json解析
@app.post('/customers')
def create_customer(request):
customer = request.json
# do something with customer
return {'success': True}
注意,这里客户端的content-Type头部必须设置application/json
URLEncoded Form Data表单提交
@app.route('/', methods=['GET', 'POST'])
def index(req):
name = 'Unknown'
if req.method == 'POST':
name = req.form.get('name')
return f'Hello {name}'
注意,这里客户端的content-Type头部必须设置application/x-www-form-urlencoded,multipart/form-data 当前还不支持
获取原始请求体
对于既不需要 JSON 数据也不需要表单数据的情况,返回整个正文 作为字节序列的请求。
如果预期的正文太大而无法放入内存,则应用程序可以使用 stream请求属性来读取正文 内容作为类似文件的对象。
Cookies
“g”
@app.before_request
def authorize(request):
username = authenticate_user(request)
if not username:
return 'Unauthorized', 401
request.g.username = username
@app.get('/')
def index(request):
return f'Hello, {request.g.username}!'
这里的g一旦设置,可以在所有路由中拿到
特定于请求的后请求处理程序
@app.post('/logout')
def logout(request):
@request.after_request
def reset_session(request, response):
response.set_cookie('session', '', http_only=True)
return response
return 'Logged out'
使用特定于请求的 Cookie 进行更新 在路由函数中定义的请求处理程序之后
请求限制
为了帮助防止恶意攻击,Microdot 提供了一些配置选项 要限制接受的信息量,请执行以下操作:
max_content_length:这 请求正文接受的最大大小(以字节为单位)。当客户端发送 大于此值的请求,服务器将以 413 错误进行响应。 默认值为 16KB。max_body_length:最大值 在body加载的大小,在 字节。正文大于此大小但更小的请求 比设置的大小只能通过属性访问。默认值也是 16KB。max_content_lengthmax_readline:允许的最大值 请求行的大小(以字节为单位)。默认值为 2KB。
以下示例将应用程序配置为接受请求 最大 1MB 的有效负载,但可防止大于 8KB 的请求 正在加载到内存中:
Request.max_content_length = 1024 * 1024
Request.max_body_length = 8 * 1024
响应对象Response
返回客户端的时候的相应
Response的三个参数
路由函数可以返回一个、两个或三个值。第一个或唯一的值是 始终在响应正文中返回到客户端:
@app.get('/')
def index(request):
return 'Hello, World!'
在上面的示例中,Microdot 发出标准 200 状态代码响应,并且 插入必要的标头。
应用程序可以提供自己的状态代码作为从 返回的第二个值 路线。下面的示例返回 202 状态代码:
@app.get('/')
def index(request):
return 'Hello, World!', 202
应用程序还可以返回第三个值,一个带有附加值的字典 添加到或替换 Microdot 提供的默认标头的标头。 下一个示例返回 HTML 响应,而不是默认文本响应:
@app.get('/')
def index(request):
return '<h1>Hello, World!</h1>', 202, {'Content-Type': 'text/html'}
如果应用程序需要返回自定义标头,但不需要更改 默认状态代码,然后它可以返回两个值,省略 stauts 状态码:
@app.get('/')
def index(request):
return '<h1>Hello, World!</h1>', {'Content-Type': 'text/html'}
JSON Responses
如果应用程序需要返回包含 JSON 格式数据的响应,它可以 返回字典或列表作为第一个值,Microdot 将 自动将响应格式化为 JSON。
@app.get('/')
def index(request):
return {'hello': 'world'}
自动添加设置为 的标头 到响应。Content-Type``application/json
重定向
from microdot import redirect
@app.get('/')
def index(request):
return redirect('/about')
响应文件
这里返回一个网页
from microdot import send_file
@app.get('/')
def index(request):
return send_file('/static/index.html')
可以在参数中将建议的缓存持续时间返回给客户端:max_age
from microdot import send_file
@app.get('/')
def image(request):
return send_file('/static/image.jpg', max_age=3600) # in seconds
注意:与其他web框架不同,Microdot不会自动配置路由来提供静态文件。以下是可以添加到应用程序的路由示例,以提供项目中静态目录中的静态文件:
@app.route('/static/<path:path>')
def static(request, path):
if '..' in path:
# directory traversal is not allowed
return 'Not found', 404
return send_file('static/' + path, max_age=86400)
Streaming Responses
@app.get('/fibonacci')
def fibonacci(request):
def generate_fibonacci():
a, b = 0, 1
while a < 100:
yield str(a) + '\n'
a, b = b, a + b
return generate_fibonacci()
更改默认响应内容类型
from microdot import Response
Response.default_content_type = 'text/html'
设置cookies
@app.get('/')
def index(request):
@request.after_request
def set_cookie(request, response):
response.set_cookie('name', 'value')
return response
return 'Hello, World!'
另一种选择是直接在路由函数中创建响应对象:
@app.get('/')
def index(request):
response = Response('Hello, World!')
response.set_cookie('name', 'value')
return response
注意:标准 Cookie 不提供足够的隐私和安全控制,因此 切勿在其中存储敏感信息,除非您要添加其他信息 加密或加密签名等保护机制。会话扩展实现已签名 防止恶意行为者篡改的 Cookie。
并发
默认情况下,Microdot 以同步(单线程)模式运行。但是,如果 该模块可用,每个请求都将在 单独的线程和请求将同时处理。threading
请注意,大多数微控制器板支持非常有限形式的 不适合并发请求处理的多线程。为 因此,不建议在微控制器平台上使用线程模块。
micropython_asyncio扩展 提供更强大的并发选项,即使在低端也受支持 MicroPython 板。
核心扩展
Microdot 是一个高度可扩展的 Web 应用程序框架。扩展 本节中描述的内容作为 Microdot 项目的一部分进行维护,并且 可以从同一源代码存储库获取。
使用Asyncio支持异步
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的microdot文件 | microdot.pymicrodot_asyncio.py |
| CPython: NoneMicroPython: uasyncio | |
| Examples | hello_async.py |
from microdot_asyncio import Microdot
app = Microdot()
@app.route('/')
async def hello(request):
return 'Hello, world!'
app.run()
html模板引擎
许多 Web 应用程序使用 HTML 模板将内容呈现给客户端。 Microdot 包括使用 CPython 上的 utemplate 包渲染模板的扩展,以及 MicroPython,并且仅在 Jinja 上 CPython。
Using the uTemplate Engine
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的microdot文件 | microdot.pymicrodot_utemplate.py |
| 额外的文件 | utemplate |
| Examples | hello.pyhello_utemplate_async.py |
render_template函数用于使用uTemplate引擎渲染HTML模板。第一个参数是相对于templates目录的模板文件名,默认情况下为templates。任何其他参数都会传递给模板引擎,用作参数。
Example:
from microdot_utemplate import render_template
@app.get('/')
def index(req):
return render_template('index.html')
加载模板的默认位置是模板子目录。可以使用以下函数更改此位置:
from microdot_utemplate import init_templates
init_templates('my_templates')
使用 Jinja Engine
| Compatibility | CPython only |
|---|---|
| 需要的microdot文件 | microdot.pymicrodot_jinja.py |
| 额外的文件 | Jinja2 |
| Examples | hello.py |
使用 Jinja 引擎渲染 HTML 模板。第一个参数是 模板文件名,相对于模板目录,即模板由 违约。任何其他参数都将传递到要使用的模板引擎 作为参数。
Example:
from microdot_jinja import render_template
@app.get('/')
def index(req):
return render_template('index.html')
加载模板的默认位置是模板子目录可以使用以下函数更改此位置:
from microdot_jinja import init_templates
init_templates('my_templates')
注意:Jinja扩展与MicroPython不兼容。
维护安全的用户会话
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的microdot文件 | microdot.pymicrodot_session.py |
| 额外的文件 | CPython: PyJWTMicroPython: jwt.py, hmac |
| Examples | login.py |
会话扩展为应用程序提供了一种安全的方式来维护 用户会话。会话作为签名的 cookie 存储在客户端的 浏览器,采用 JSON Web 令牌 (JWT) 格式。
要使用用户会话,应用程序首先必须配置密钥 这将用于对会话 Cookie 进行签名。非常重要的是,这 密钥是保密的。拥有此密钥的攻击者可以生成 包含任何内容的有效用户会话 Cookie。
要设置密钥,请使用以下函数:set_session_secret_key
from microdot_session import set_session_secret_key
set_session_secret_key('top-secret!')
函数 在路由处理程序中分别检索、存储和删除会话数据。 提供装饰器 作为在路由处理程序开始时检索会话的便捷方法
Example:
from microdot import Microdot
from microdot_session import set_session_secret_key, with_session, update_session, delete_session
app = Microdot()
set_session_secret_key('top-secret')
@app.route('/', methods=['GET', 'POST'])
@with_session
def index(req, session):
username = session.get('username')
if req.method == 'POST':
username = req.form.get('username')
update_session(req, {'username': username})
return redirect('/')
if username is None:
return 'Not logged in'
else:
return 'Logged in as ' + username
@app.post('/logout')
def logout(req):
delete_session(req)
return redirect('/')
Cross-Origin Resource Sharing (CORS)
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的microdot文件 | microdot.pymicrodot_cors.py |
| 额外的文件 | None |
| Examples | cors.py |
跨域支持
To enable CORS support, create an instance of the CORS class and configure the desired options. Example:
from microdot import Microdot
from microdot_cors import CORS
app = Microdot()
cors = CORS(app, allowed_origins=['https://example.com'],
allow_credentials=True)
WebSocket 支持
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的microdot文件 | microdot.pymicrodot_websocket.py |
| 额外的文件 | None |
| Examples | echo.pyecho_wsgi.py |
使用with_websocket装饰器,注入ws,提供send(),和receive()方法
Example:
@app.route('/echo')
@with_websocket
def echo(request, ws):
while True:
message = ws.receive()
ws.send(message)
注意
不受支持的microdot_websocket_alt.py模块,具有相同的 接口,也提供了。此模块使用本机 WebSocket 支持 在MicroPython中,它为WebREPL提供支持,并且可能会提供更好的 MicroPython 低端板的性能。此模块不兼容 与CPython。
异步 WebSocket
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的文件 | microdot.pymicrodot_asyncio.pymicrodot_websocket.pymicrodot_asyncio_websocket.py |
| 额外的文件 | CPython: NoneMicroPython: uasyncio |
| Examples | echo_async.py |
和websocket一样,但是方法是异步的
Note
还提供了具有相同接口的不受支持的microdot_asgi_websocket.py模块。当使用ASGI支持时,必须使用此模块而不是microdot_asyncio_websocket.py。echo_asgi.py示例显示了如何使用此模块。
HTTPS 支持
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的文件 | microdot.pymicrodot_ssl.py |
| Examples | hello_tls.pyhello_async_tls.py |
该函数接受一个可选参数,通过该参数 可以传递初始化的对象。MicroPython 目前没有 有一个实现,所以模块提供 可用于创建上下文的基本实现
Example:
from microdot import Microdot
from microdot_ssl import create_ssl_context
app = Microdot()
@app.route('/')
def index(req):
return 'Hello, World!'
sslctx = create_ssl_context('cert.der', 'key.der')
app.run(port=4443, debug=True, ssl=sslctx)
Note
The microdot_ssl module is only needed for MicroPython.
When used under CPython, this module creates a standard SSLContext instance.
Note
The uasyncio library for MicroPython does not currently support TLS, so this feature is not available for asynchronous applications on that platform. The asyncio library for CPython is fully supported.
Test Client
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的文件 | microdot.pymicrodot_test_client.py |
| 额外的文件 | None |
Microdot 测试客户端是一个实用程序类,可在测试期间用于 将请求发送到应用程序中。
Example:
from microdot import Microdot
from microdot_test_client import TestClient
app = Microdot()
@app.route('/')
def index(req):
return 'Hello, World!'
def test_app():
client = TestClient(app)
response = client.get('/')
assert response.text == 'Hello, World!'
See the documentation for the TestClient class for more details.
异步 Test Client
| Compatibility | CPython & MicroPython |
|---|---|
| 需要的文件 | microdot.pymicrodot_asyncio.pymicrodot_test_client.pymicrodot_asyncio_test_client.py |
| 额外的文件 | None |
Similar to the TestClient class above, but for asynchronous applications.
Example usage:
from microdot_asyncio_test_client import TestClient
async def test_app():
client = TestClient(app)
response = await client.get('/')
assert response.text == 'Hello, World!'
See the reference documentation for details.
在生产 Web 服务器上部署
该类创建自己的简单 Web 服务器。这足以让 使用MicroPython部署的应用程序,但是当使用CPython时,它可能很有用 使用单独的、经过实战考验的 Web 服务器。为了满足这一需求,Microdot 提供实现 WSGI 和 ASGI 协议的扩展。Microdot
| Compatibility | CPython only |
|---|---|
| 需要的文件 | microdot.pymicrodot_wsgi.py |
| 额外的文件 | A WSGI web server, such as Gunicorn. |
| Examples | hello_wsgi.py |
The microdot_wsgi module provides an extended Microdot class that implements the WSGI protocol and can be used with a compliant WSGI web server such as Gunicorn or uWSGI.
To use a WSGI web server, the application must import the Microdot class from the microdot_wsgi module:
from microdot_wsgi import Microdot
app = Microdot()
@app.route('/')
def index(req):
return 'Hello, World!'
The app application instance created from this class is a WSGI application that can be used with any complaint WSGI web server. If the above application is stored in a file called test.py, then the following command runs the web application using the Gunicorn web server:
gunicorn test:app
Using an ASGI Web Server
| Compatibility | CPython only |
|---|---|
| 需要的文件 | microdot.pymicrodot_asyncio.pymicrodot_asgi.py |
| 额外的文件 | An ASGI web server, such as Uvicorn. |
| Examples | hello_asgi.py |
The microdot_asgi module provides an extended Microdot class that implements the ASGI protocol and can be used with a compliant ASGI server such as Uvicorn.
To use an ASGI web server, the application must import the Microdot class from the microdot_asgi module:
from microdot_asgi import Microdot
app = Microdot()
@app.route('/')
async def index(req):
return 'Hello, World!'
The app application instance created from this class is an ASGI application that can be used with any complaint ASGI web server. If the above application is stored in a file called test.py, then the following command runs the web application using the Uvicorn web server:
uvicorn test:app