A Sonoff B05BL in DIY mode randomly refuses POSTs sent through libwebsockets — it answers "read failed" and the bulb doesn't switch. The same request sent with curl or a raw socket works every time. Capturing the traffic, the bytes lws sends are identical to curl's, but the device never ACKs lws's first data packet, so the request is lost and times out.
Environment: libwebsockets 3e8ec88, LWS_WITH_LIBUV=ON, foreign libuv loop, cleartext HTTP/1.1.
Repository: https://github.com/gly-engine/third-party-issues/tree/main/sonoff-warmcat-libwebsocket
Server: Sonoff B05BL, DIY mode, POST /zeroconf/switch with JSON body and Connection: close. Reports Server: openresty but it's really a bare lwIP/ESP stack (win 4380, MSS 1460, no SACK/timestamps).
Works every time with curl:
curl -X POST -H "Content-Type: application/json" \
-d '{"deviceid":"10023d8f20","data":{"switch":"on"}}' \
http://192.168.3.233:8081/zeroconf/switch
Works every time raw:
printf 'POST /zeroconf/switch HTTP/1.1\r\nHost: x\r\nContent-Type: application/json\r\nContent-Length: 48\r\nConnection: close\r\n\r\n{"deviceid":"10023d8f20","data":{"switch":"on"}}' \
| nc 192.168.3.233 8081
Fails intermittently (sometimes always) through lws with LWS_CALLBACK_CLIENT_CONNECTION_ERROR = "read failed".
Observations
- tcpdump: curl/raw → device ACKs + 145-byte response → close (~28ms). lws → first data segment un-ACKed ~244ms → TCP retransmit → device ACKs but answers nothing, sits ~3s, FINs empty →
"read failed".
- The device has a very fragile/non-standard HTTP parser, and that's exactly what the lws failure looks like. If the request is even slightly off —
Content-Length bigger than the body actually sent, no Content-Length, a path without the leading slash, or HTTP/1.0 — it doesn't reject it cleanly: it just hangs for a few seconds and closes with no response, which curl reports as curl: (52) Empty reply from server. The lws failure is the same empty-reply behavior. So from the device's point of view the lws request seems to arrive malformed/incomplete (e.g. it got headers promising a body but not all of it in time), even though the captured bytes match a working curl request.
- lws request is byte-identical to curl's (same line/headers/body, single segment).
- Headers (Pragma/Cache-Control/Origin lws adds): device 200s with them via curl — not the cause.
- HTTP/2: cleartext stays H1; building
-DLWS_ROLE_H2=OFF changes nothing.
- Header+body in one segment vs the default separate body write — no change.
- Delay 0–50ms between connect and send (raw socket) — 48/48 ok.
- Blocking vs non-blocking connect (raw socket) — both 100%.
- Wi-Fi is stable and curl passes 100% of the tests expected to pass — so this isn't link flakiness, it's specific to lws.
A Sonoff B05BL in DIY mode randomly refuses POSTs sent through libwebsockets — it answers
"read failed"and the bulb doesn't switch. The same request sent with curl or a raw socket works every time. Capturing the traffic, the bytes lws sends are identical to curl's, but the device never ACKs lws's first data packet, so the request is lost and times out.Environment: libwebsockets
3e8ec88,LWS_WITH_LIBUV=ON, foreign libuv loop, cleartext HTTP/1.1.Repository: https://github.com/gly-engine/third-party-issues/tree/main/sonoff-warmcat-libwebsocket
Server: Sonoff B05BL, DIY mode,
POST /zeroconf/switchwith JSON body andConnection: close. ReportsServer: openrestybut it's really a bare lwIP/ESP stack (win 4380, MSS 1460, no SACK/timestamps).Works every time with curl:
Works every time raw:
Fails intermittently (sometimes always) through lws with
LWS_CALLBACK_CLIENT_CONNECTION_ERROR="read failed".Observations
"read failed".Content-Lengthbigger than the body actually sent, noContent-Length, a path without the leading slash, orHTTP/1.0— it doesn't reject it cleanly: it just hangs for a few seconds and closes with no response, which curl reports ascurl: (52) Empty reply from server. The lws failure is the same empty-reply behavior. So from the device's point of view the lws request seems to arrive malformed/incomplete (e.g. it got headers promising a body but not all of it in time), even though the captured bytes match a working curl request.-DLWS_ROLE_H2=OFFchanges nothing.