有谁知道为什么 IOS 的 HTTP Post 表单上的响应 Body 会是空的?它适用于 Android

Does anyone know why the Response Body would be empty on an HTTP Post form from IOS. It works in Android

您好,我们正在尝试使用此 WIFI 管理器 (https://github.com/tayfunulu/WiFiManager/blob/master/wifimgr.py) 将 ESP 32 设备连接到本地 wifi。

WIFI 管理器基本上可以识别本地的所有 SSID,在 192.168.4.1 上启动 Web 服务器,显示不同的网络并让用户 select 他们想要连接的网络。此过程在移动浏览器(Android 或 IOS 手机和任何浏览器)上完成。

这在 Android 中完美运行,但 IOS returns 空 POST 响应 body。我使用了 IOS Charles,可以看到包含正确信息的响应 body,但它没有返回到服务器。

Image shows Response Header

Image two shows response body

这是服务器在 Andriod 中收到的内容(流末尾的粗体数据):

客户端连接自 ('192.168.4.3', 47754) 请求是:b'POST /configure HTTP/1.1\r\nHost: 192.168.4.1\r\nConnection: keep-alive\r\nContent-Length: 39\r\nCache-Control: max-age=0\r\nUpgrade-Insecure-Requests: 1\r\nOrigin: http://192.168.4.1\r\nContent-Type: application/x-www-form-urlencoded\r\nUser-Agent: Mozilla/5.0 (Linux; Android 7.0) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/83.0.4103.106 Mobile DuckDuckGo/5 Safari/537.36\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nX-Requested-With: com.duckduckgo.mobile.android\r\nReferer: http://192.168.4.1/\r\nAccept-Encoding: gzip, deflate\r\nAccept-Language: en-US,en;q=0.9\r\n\r\nssid=mywifi&password=abcdef1234'

这是服务器在 IOS 中收到的内容(流末尾没有数据):

客户端连接自 ('192.168.4.2', 64482) 请求是:b'POST /configure HTTP/1.1\r\nHost: 192.168.4.1\r\nOrigin: http://192.168.4.1\r\nContent-Type: application/x-www-form-urlencoded\r\nAccept-Encoding: gzip, deflate\r\nConnection: keep-alive\r\nUpgrade-Insecure-Requests: 1\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,/;q=0.8\r\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 DuckDuckGo/7 \r\nReferer: http://192.168.4.1/\r\nContent-Length: 39\r\nAccept-Language: en-us\r\n\r\n'

我意识到 Charles 响应 header 和数据与上面的 IOS 段落不同,但结果相同。我在 iphone 上尝试了 Safari、Chrome 和 FF,结果都相同。我已确保隐私设置已设置为允许第三方数据,但也许还有一个地方我错过了。提供下面的表单操作代码 (def handle_root(client),它会显示所有本地 wifi 网络的屏幕,下面是 Web 服务器启动 (Def start (port=80))。

谢谢,吉姆

用户表单 select 要连接到的 SSID:

def send_header(client, status_code=200, content_length=None ):
    client.sendall("HTTP/1.0 {} OK\r\n".format(status_code))
    client.sendall("Content-Type: text/html\r\n")
    if content_length is not None:
       client.sendall("Content-Length: {}\r\n".format(content_length))
    client.sendall("\r\n")

def handle_root(client):
wlan_sta.active(True)
ssids = sorted(ssid.decode('utf-8') for ssid, *_ in wlan_sta.scan())
send_header(client)
client.sendall("""\
    <html>
        <h1 style="color: #5e9ca0; text-align: center;">
            <span style="color: #ff0000;">
                Wi-Fi Client Setup
            </span>
        </h1>
        <form action="configure" method="post">
            <table style="margin-left: auto; margin-right: auto;">
                <tbody>
""")
while len(ssids):
    ssid = ssids.pop(0)
    client.sendall("""\
                    <tr>
                        <td colspan="2">
                            <input type="radio" name="ssid" value="{0}" />{0}
                        </td>
                    </tr>
    """.format(ssid))
client.sendall("""\
                    <tr>
                        <td>Password:</td>
                        <td><input name="password" type="password" /></td>
                    </tr>
                </tbody>
            </table>
            <p style="text-align: center;">
                <input type="submit" value="Submit" />
            </p>
        </form>
        <p>&nbsp;</p>
        <hr />
        <h5>
            <span style="color: #ff0000;">
                Your ssid and password information will be saved into the
                "%(filename)s" file in your ESP module for future usage.
                Be careful about security!
            </span>
        </h5>
        <hr />
        <h2 style="color: #2e6c80;">
            Some useful infos:
        </h2>
        <ul>
            <li>
                Original code from <a href="https://github.com/cpopp/MicroPythonSamples"
                    target="_blank" rel="noopener">cpopp/MicroPythonSamples</a>.
            </li>
            <li>
                This code available at <a href="https://github.com/tayfunulu/WiFiManager"
                    target="_blank" rel="noopener">tayfunulu/WiFiManager</a>.
            </li>
        </ul>
    </html>
""" % dict(filename=NETWORK_PROFILES))
client.close()

def start(port=80):
global server_socket

addr = socket.getaddrinfo('0.0.0.0', port)[0][-1]

stop()

wlan_sta.active(True)
wlan_ap.active(True)

wlan_ap.config(essid=ap_ssid, password=ap_password, authmode=ap_authmode)

server_socket = socket.socket()
server_socket.bind(addr)
server_socket.listen(1)

print('Connect to WiFi ssid ' + ap_ssid + ', default password: ' + ap_password)
print('and access the ESP via your favorite web browser at 192.168.4.1.')
print('Listening on:', addr)

while True:
    if wlan_sta.isconnected():
        return True

    client, addr = server_socket.accept()
    print('client connected from', addr)
    try:
        client.settimeout(5.0)

        request = b""
        try:
            while "\r\n\r\n" not in request:
                request += client.recv(512)
        except OSError:
            pass

        print("Request is: {}".format(request))
        if "HTTP" not in request:  # skip invalid requests
            continue

        # version 1.9 compatibility
        try:
            url = ure.search("(?:GET|POST) /(.*?)(?:\?.*?)? HTTP", request).group(1).decode("utf-8").rstrip("/")
        except Exception:
            url = ure.search("(?:GET|POST) /(.*?)(?:\?.*?)? HTTP", request).group(1).rstrip("/")
        print("URL is {}".format(url))

        if url == "":
            handle_root(client)
        elif url == "configure":
            handle_configure(client, request)
        else:
            handle_not_found(client, url)

    finally:
        client.close()

WIFI Manager Workflow

终于明白了。事实证明,对于熟悉 HTTP 套接字协议的人来说这可能是显而易见的,但是对于 Android 移动设备 OS,响应 body 会在末尾返回 header ,对于 IOS,您必须执行另一个“recv”来检索响应 body。希望这对某人有所帮助,并为他们节省数周的挖掘和焦虑。