【发布时间】:2021-11-23 11:10:32
【问题描述】:
我正在尝试基于 MicroPython 的基本 socket 模块实现一个简单的 http 服务器,该模块提供静态 html,可以接收和处理简单的 http GET 和 POST 请求以在 ESP 上保存一些数据。
我按照本教程https://randomnerdtutorials.com/esp32-esp8266-micropython-web-server/ 更改了一些部分。
webserver.py
import logging
from socket import socket, getaddrinfo, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
from request import Request
from request.errors import PayloadError
log = logging.getLogger(__name__)
def __read_static_html(path: str) -> bytes:
with open(path, "rb") as f:
static_html = f.read()
return static_html
def __create_socket(address: str = "0.0.0.0", port: int = 8080) -> socket:
log.info("creating socket...")
s = socket(AF_INET, SOCK_STREAM)
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
addr = getaddrinfo(address, port)[0][-1]
s.bind(addr)
log.info("socket bound on {} ".format(addr))
return s
def __read_from_connection(conn: socket) -> Request:
log.info("read from connection...")
raw_request = conn.recv(4096)
return Request(raw_request.decode("utf-8"))
def listen_and_serve(webfile: str, address: str, port: int):
server_socket = __create_socket(address, port)
log.info("listen on server socket")
server_socket.listen(5)
while True:
# accept connections
client_server_connection, client_address = server_socket.accept()
log.info("connection from {}".format(client_address))
req = __read_from_connection(client_server_connection)
log.info("got request: {}".format(req.get_method()))
path = req.get_path()
if path != '/':
log.info("invalid path: {}".format(path))
client_server_connection.send(b"HTTP/1.1 404 Not Found\n")
client_server_connection.send(b"Connection: close\n\n")
client_server_connection.close()
continue
if req.get_method() == "POST":
log.info("handle post request")
try:
pl = req.get_payload()
log.debug(pl)
except PayloadError as e:
log.warning("error: {}".format(e))
client_server_connection.send(b"HTTP/1.1 400 Bad Request\n")
client_server_connection.send(b"Connection: close\n\n")
client_server_connection.close()
continue
log.info("read static html...")
static_html = __read_static_html(webfile)
log.info("send header...")
client_server_connection.send(b"HTTP/1.1 200 OK\n")
client_server_connection.send(b"Connection: close\n\n")
log.info("send html...")
client_server_connection.sendall(static_html)
log.info("closing client server connection")
client_server_connection.close()
请求模块是我自己编写的 http 请求解析器,对我需要的支持最少。
提供的 html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP Basic Configuration</title>
</head>
<body>
<h1>ESP Basic Configuration</h1>
<form action="/" method="post" enctype="application/x-www-form-urlencoded">
<h3>Network</h3>
<div>
<label for="network.ssid">SSID</label>
<input id="network.ssid" name="network.ssid" type="text" />
</div>
<div>
<label for="network.password">Password</label>
<input id="network.password" name="network.password" type="password" />
</div>
<button type="submit">Save</button>
</form>
</body>
</html>
当我使用正常的 Python3.9 在我的系统上运行代码时,一切似乎都正常。
在我的 ESP8266 上运行代码,raw_request 的长度被截断为 536 字节。所以有些请求不完整,payload无法读取。
我已经阅读了默认情况下套接字是非阻塞的,并且可能会发生短读。我尝试过使用带有超时的阻塞套接字。但我总是有超时,我认为不应该有。
我尝试过使用类似文件的套接字对象,如下所示:
https://docs.micropython.org/en/latest/esp8266/tutorial/network_tcp.html#simple-http-server
但是由于\r\n 的if 条件,请求读取在标头之后停止。
删除此条件并仅检查 if not line 会在下一行读取循环。
我目前不知道如何才能获取带有有效负载的完整请求。
编辑:添加 MRE 这是我可以重现该问题的最小示例:
main.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
def main():
server_socket = socket(AF_INET, SOCK_STREAM)
server_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server_socket.bind(("0.0.0.0", 8080))
server_socket.listen(5)
while True:
# accept connections
client_server_connection, client_address = server_socket.accept()
raw_request = client_server_connection.recv(4096)
print("raw request length: {}".format(len(raw_request)))
print(raw_request)
client_server_connection.send(b"HTTP/1.1 200 OK\r\n")
client_server_connection.send(b"Connection: close\r\n\r\n")
client_server_connection.sendall(b"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ESP Basic Configuration</title>
</head>
<body>
<h1>ESP Basic Configuration</h1>
<form action="/" method="post" enctype="application/x-www-form-urlencoded">
<h3>Network</h3>
<div>
<label for="network.ssid">SSID</label>
<input id="network.ssid" name="network.ssid" type="text" />
</div>
<div>
<label for="network.password">Password</label>
<input id="network.password" name="network.password" type="password" />
</div>
<button type="submit">Save</button>
</form>
</body>
</html>
""")
client_server_connection.close()
if __name__ == "__main__":
main()
当使用打印语句输出原始请求时,我得到以下信息:
raw request length: 536
b'POST / HTTP/1.1\r\nHost: 192.168.0.113:8080\r\nConnection: keep-alive\r\nContent-Length: 39\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nOrigin: http://192.168.0.113:8080\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nReferer: http://192.168.0.113:8080/\r\nA'
请求仍被截断为 536 字节,请求突然结束。
【问题讨论】:
-
你能 MRE 这个吗?删除邮政编码并减少到最简单的错误。如果没有发布任何内容,我将在接下来的几天内尝试。不过,您可能只是达到了 esp8266 的限制:使用 uPy 的 esp32 体验要好得多
-
@2e0byo MRE?对不起,我在这里很新。这是什么意思?
-
@2e0byo 我要将 MRE 添加到我原来的问题中。
标签: python sockets http webserver micropython