POST 从 espduino 到 Flask 超时

POST to Flask from espduino timing out

我正在玩 esp8266 providing WiFi to an Arduino with this library. I have it set up properly to POST to Pushover as well as requestbin,在这些工具的调试输出和界面之间,我可以验证请求数据是否正确地被 POSTed,并且 esp8266 / arduino 单元的响应状态代码正确显示 200。

我想测试设置的可靠性,所以我想我会求助于 Flask。我将 for 循环放入 POST 100 个请求,从 espduino 到 Flask 应用程序 运行ning 0.0.0.0:5000。 POST 包含一个测试字符串(尝试粗略衡量简单的数据完整性,确保字符串未损坏)以及正在发送的循环数(例如,第一个循环为 0 .. . 99 最后)。 Flask 跟踪这些请求并更新其输出以显示哪些请求正确通过,哪些没有正确通过。

除一件事外,设置运行良好:模块在每次请求后超时。每个 POST 似乎都有效,Flask 适当地更新了输出,但是 espduino 对每个请求花费了整整 5 秒(默认超时),并表示它得到了 0.[=38 的响应代码=]

所以重申一下情况:我的 espduino 设置正确 POSTs 并从​​ requestb.in 和 pushover.net 获得 200 响应,但是我的本地 Flask 服务器 POSTs 然后超时。

WHYT:

我花了几天时间研究这个 not made much progress。我今天最大的突破是,如果我将我的 gunicorn / Flask 设置放在 nginx 后面,我可以成功 post 并获得 200 个响应,并且 no 更改 espduino 代码,所以我确定 Flask 正在做或没有做某事(我担心这可能是 espduino 对 IP 地址与域名的处理,但我认为这排除了这一点)。

我尝试过的设置总结:

当前代码:

from flask import Flask, request, make_response
from datetime import datetime

app = Flask(__name__)

ESPDUINO_IP = 'XXX.XXX.XXX.XXX'
INCOMING_TEST_STRING = 'This is my espduino test string!'
incoming_requests = []

with open('results.txt', 'w') as f:
    f.write("Test run start: {}\n".format(datetime.now()))

@app.route('/api', methods=['GET', 'POST'])
def count_requests():
    if request.method == 'POST':
        form = request.form
        incoming_ip = request.remote_addr
        if incoming_ip == ESPDUINO_IP and form['test_string'] == INCOMING_TEST_STRING:
            test_num = int(form['test_num'])
            incoming_requests.append(test_num)
            msg = "All is peachy!"
            with open('results.txt', 'a') as f:
                f.write("{:02d}: {}\n".format(test_num, datetime.now()))
    else:
        msg = "Hey, you're not the espduino!<br>"
        msg += str(len(incoming_requests)) + " requests so far.<br>"
        missing = set(range(100)) - set(incoming_requests)
        msg += "Missing: {}<br>".format(', '.join(map(str, missing)) if missing else "None!")
        msg += '<br>'.join(map(str, incoming_requests))

    resp = make_response('{"this": "that"}')
    resp.headers['Content-Type'] = "application/json"
    return resp
    # return "<html><body>{}</body></html>".format(msg)

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)

这是来自 espduino 的 POST 的样子:

$ nc -l 5000
POST /api HTTP/1.1
Host: XXX.XXX.XXX.XXX
Content-Length: 55
Connection: close
Content-Type: application/x-www-form-urlencoded
User-Agent: ESPDRUINO@tuanpm

test_string=This is my espduino test string!&test_num=0

curl -X POST -d 'test_string=This is my espduino test string!&test_num=0' localhost:5000/api相比:

$ nc -l 5000
POST /api HTTP/1.1
Host: localhost:5000
User-Agent: curl/7.43.0
Accept: */*
Content-Length: 55
Content-Type: application/x-www-form-urlencoded

test_string=This is my espduino test string!&test_num=0

很想听听有关可能发生的事情的任何想法。我想知道这是否是 WSGI 问题?

2015 年 8 月 31 日更新:

我还没有弄明白,但这绝对不是 Flask 特有的问题。如上所述,我还使用 CherryPy 以及 python3 -m http.server --bind 0.0.0.0 5000(并将 espduino 代码更改为 GET /)以及 ruby -run -e httpd 复制了超时。还是不明白为什么nginx、requestbin等服务起来没问题

为了回应@Miguel 关于 HOST header 没有端口的评论,我正在努力分叉和构建固件来改变它,但与此同时我 hard-coded 客户端主机和端口到一个小的 HTTP 服务器脚本,没有运气。

from http.server import BaseHTTPRequestHandler, HTTPServer

class MyServer(BaseHTTPRequestHandler):
    # protocol_version = 'HTTP/1.1'
    # close_connection = True
    def _set_headers(self):
        self.send_response(200)
        self.send_header('Content-type', 'text/html')
        self.end_headers()

    def do_GET(self):
        self._set_headers()
        self.wfile.write(b"<html><body><h1>hi!</h1></body></html>")

    def do_HEAD(self):
        self._set_headers()

    def do_POST(self):
        self.client_address = ("192.168.0.4", 5000)
        self._set_headers()
        self.wfile.write(b"<html><body><h1>POST!</h1></body></html>")
        # import pdb; pdb.set_trace()

def run(server_class=HTTPServer, handler_class=MyServer, port=5000):
    server_address = ('0.0.0.0', port)
    httpd = server_class(server_address, handler_class)
    print('Starting httpd...')
    httpd.serve_forever()

if __name__ == "__main__":
    run()

通过 tcpdump 查看是否可以找到工作 (nginx) 和非工作网络数据之间的任何差异。到目前为止还没有发现任何东西,但我也是这个工具的新手。

2015 年 9 月 8 日更新

仍然没有弄清楚,但看起来 tcpdump 在 nginx 和 Python 服务器之间有很大的不同。这是一个示例 POST 和响应——为了清楚起见,我已将 IP 替换为 ESPDUINO_IPOSX_IP,并清理了周围的 ACK 调用等。我需要调查 Python 响应被中断的原因 b那条奇怪的线——我检查了 10 多个连续的 POST / 响应对,Python 响应中的每个 都像那样被打断了(恰好在同一个地方)和 none 的 nginx 响应是,所以我想知道这是否可能是问题所在。 (此外,如您所见,在这一轮测试期间,我将响应 body 更改为文本而不是 JSON -- 结果没有变化。)

nginx(有效)
POST /api HTTP/1.1
Host: OSX_IP
Content-Length: 29
Connection: close
Content-Type: application/x-www-form-urlencoded; charset=utf-8
User-Agent: espduino@n8henrie

test_string=simple&test_num=0


09:16:04.079291 IP OSX_IP.commplex-main > ESPDUINO_IP.49146: Flags [P.], seq 1:183, ack 211, win 65535, length 182

HTTP/1.1 200 OK
Server: nginx/1.8.0
Date: Mon, 31 Aug 2015 15:16:04 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 26
Connection: close

<html><body></body></html>
烧瓶(超时)
POST /api HTTP/1.1
Host: OSX_IP
Content-Length: 29
Connection: close
Content-Type: application/x-www-form-urlencoded; charset=utf-8
User-Agent: espduino@n8henrie

test_string=simple&test_num=3

09:00:19.424086 IP OSX_IP.commplex-main > ESPDUINO_IP.48931: Flags [P.], seq 1:18, ack 211, win 65535, length 17

HTTP/1.0 200 OK

09:00:36.382125 IP OSX_IP.commplex-main > ESPDUINO_IP.48931: Flags [FP.], seq 18:181, ack 211, win 65535, length 163
E....F@.@..,...e.......#...k..S.P.......Content-Type: text/html; charset=utf-8
Content-Length: 26
Server: Werkzeug/0.10.4 Python/3.4.3
Date: Mon, 31 Aug 2015 15:00:36 GMT

<html><body></body></html>

在我看来 Python 出于某种原因将响应分成两半,例如length 17 的一部分和 length 163 的另一部分,与 nginx 的单一响应 length 182.

相比

2015 年 9 月 10 日更新

有趣的是,如果我 运行 通过 mitmproxy 一切都按预期工作——甚至直接到 Flask 应用程序而不用 nginx 或 gunicorn。一旦我删除 mitmproxy,它就会回到上面的超时状态。

仍然没有解决问题,但我想我可能已经弄清楚是什么原因造成的。毕竟不是 Flask 的问题。

不幸的是,这似乎是 esp_bridge 库的错误,其固件 espduino 在 esp8266 中使用。请原谅可能不正确的术语,但出于某种原因,据我所知,它似乎并没有加入 TCP 数据包。产生 HTTP 响应的服务器被拆分成单独的 TCP 数据包(例如 Flask)失败,而 tcpdump 可以验证 nginx 和 mitmproxy 是否正在加入拆分的 TCP 数据包并在单个数据包中返回响应,这就是它们工作的原因.

https://github.com/tuanpmt/esp_bridge/issues/10

更新20160128

我今天重新讨论了这个问题 found a workaround。虽然理想的解决方案是修复 esp_bridge 以重新组装多数据包响应,但只要响应非常小,就可以强制 Flask 将响应写入单个数据包。

from werkzeug.serving import WSGIRequestHandler

# Your Flask code here...

if __name__ == "__main__":
    WSGIRequestHandler.wbufsize = -1
    app.run()