MicroPython和ESP8266 http服务器不完整请求



我正在尝试实现一个基于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 Foundn")
client_server_connection.send(b"Connection: closenn")
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 Requestn")
client_server_connection.send(b"Connection: closenn")
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 OKn")
client_server_connection.send(b"Connection: closenn")
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字节。所以有些请求是不完整的,负载不能被读取。

我已经读取套接字默认情况下是非阻塞的,可以发生短读。我试过使用超时阻塞套接字。但是我总是有超时,当我认为不应该有一个。

我尝试使用类似文件的套接字对象,如下所示:
https://docs.micropython.org/en/latest/esp8266/tutorial/network_tcp.html#simple-http-server
但是由于rn的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 OKrn")
client_server_connection.send(b"Connection: closernrn")
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()

当用print语句输出原始请求时,我得到以下信息:

raw request length: 536
b'POST / HTTP/1.1rnHost: 192.168.0.113:8080rnConnection: keep-alivernContent-Length: 39rnCache-Control: max-age=0rnUpgrade-Insecure-Requests: 1rnOrigin: http://192.168.0.113:8080rnContent-Type: application/x-www-form-urlencodedrnUser-Agent: Mozilla/5.0 (X11; Fedora; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36rnAccept: 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.9rnReferer: http://192.168.0.113:8080/rnA'

请求仍然被截断为536字节,并且请求突然结束。

def __read_from_connection(conn: socket) -> Request:
log.info("read from connection...")
raw_request = conn.recv(4096)

您假设recv将返回所需的所有数据。这种假设是错误的。TCP没有消息的概念,而是一个字节流,所以你需要一次又一次地执行recv,直到你得到你需要的所有数据。all data的含义由HTTP消息格式定义——参见RFC 7230中的HTTP标准。

为什么recv返回的数据量在ESP8266和PC之间不同:可能是因为ESP8266有一个较小的套接字缓冲区,并在TCP中将其宣布为窗口。这使得对端以较小的数据包发送数据。

除此之外,以响应结尾的行必须是rn而不是n

但是由于rn的if条件,请求读取在标头之后停止。删除此条件并使用if not line进行检查,可以保持下一行读取的循环。

空行(rn)标志着结束的HTTP头。为了找出正文的大小,你需要解析HTTP报头并查找长度信息,即Content-length报头为固定长度或Transfer-Encoding: chunked报头,当正文以小块发送并且不知道完整长度时。详见标准。

最新更新