Answer a GET to /ota instead of a 405

b81a582 · Gabriel Morell · 2026-09-16 20:46

3 files +33 -2
Message
{commit_body(@commit)}

Files changed

modified README.md
+7 −2
@@ -152,8 +152,9 @@ build.
152 152 ### Driving it by hand
153 153
154 154 `GET /` says what a box is running — note it answers **218**, not 200, which
155 −the packet sender matches on — and `POST /ota` makes it go and look now rather
156 −than at the next interval:
155 +the packet sender matches on. `GET /ota` reports what the updater last found
156 +and does nothing; **`POST /ota`** is what makes it go and look now rather than
157 +at the next interval:
157 158
158 159 ```console
159 160 $ curl -s http://172.16.15.139/ | jq '{version, ota_slot, ota_state, ota_available}'
@@ -167,6 +168,10 @@ $ curl -s http://172.16.15.139/ | jq '{version, ota_slot, ota_state, ota_availab
167 168 $ curl -s -i -X POST http://172.16.15.139/ota | head -1
168 169 HTTP/1.1 202 Accepted
169 170
171 +$ # a GET is safe, and says so
172 +$ curl -s http://172.16.15.139/ota | jq -r .hint
173 +POST to this URL to check for an update now
174 +
170 175 $ curl -s -X POST http://172.16.15.139/ota | jq
171 176 {
172 177 "queued": true,
modified src/main.rs
+26 −0
@@ -705,6 +705,7 @@ fn main() -> Result<(), Error> {
705 705 let status_ota_slot = ota_slot.clone();
706 706 let status_ota_state = ota_state.clone();
707 707 let ota_updates = updates.clone();
708 + let ota_report = updates.clone();
708 709 let mut status = StatusServer::bare()?.asset("/status", assets::STATUS_HTML)?;
709 710
710 711 // Registered by hand rather than via `StatusServer::json`, which hardcodes
@@ -746,6 +747,31 @@ fn main() -> Result<(), Error> {
746 747 Ok::<(), EspIOError>(())
747 748 })?;
748 749
750 + // The same report, without doing anything — because `/ota` is a URL
751 + // people paste into a browser, and a bare GET against a POST-only handler
752 + // is a 405 from ESP-IDF's server (httpd_uri.c: URI found, method not
753 + // allowed), which reads like a broken device rather than like "use POST".
754 + //
755 + // A GET that triggered an update would be worse than unfriendly: anything
756 + // that prefetches a link would reboot the device.
757 + status
758 + .server_mut()
759 + .fn_handler("/ota", Method::Get, move |req| {
760 + let report = ota_report.report();
761 + let resp = json!({
762 + "running": ota::running_version(),
763 + "available": report.available,
764 + "last_error": report.last_error,
765 + "checked": report.checked,
766 + // Said plainly, since the reason to be reading this is
767 + // usually "why has nothing happened".
768 + "hint": "POST to this URL to check for an update now",
769 + });
770 + req.into_response(200, None, &[("Content-Type", "application/json")])?
771 + .write_all(resp.to_string().as_bytes())?;
772 + Ok::<(), EspIOError>(())
773 + })?;
774 +
749 775 // Force a check now, for a device that should not wait out its interval.
750 776 // Deliberately not a GET: it has an effect, and a device that reboots
751 777 // itself because something prefetched a link would be its own bug.

Parents: b36ef03