Files
firmware/src/mesh/eth/ethApiServer.cpp
T
Ben Meadors 0ff10318ad refactor(net): unique_ptr for connection-lifecycle objects (#11459)
- WiFiServerAPI/ethServerAPI apiPort and ethApiServer's listener are
  create/destroy cycles that repeat across WiFi teardown and W5500
  chip resets; the manual delete+null bookkeeping becomes reset().
  (ethTlsApiServer's listener is left for a follow-up: that file is
  already touched by the partial-init fix PR and converting it here
  would conflict.)

- ContentHandler::handleFormUpload held its body parser raw with
  delete on four separate exit paths of a per-request handler; any
  future early return was a silent leak. unique_ptr removes all four.

- The portduino ch341Hal global becomes unique_ptr. The LoRa-error
  recovery loop's delete/null/new sequence was correct only by
  hand-preserved ordering; it becomes reset()/make_unique. RadioLibHAL
  keeps a non-owning raw pointer, as before.

No behavior change.
2026-08-14 10:04:57 +00:00

111 lines
3.8 KiB
C++

#include "configuration.h"
#if HAS_ETHERNET && defined(HAS_ETHERNET_API)
#include "concurrency/OSThread.h"
#include "ethApiHandlers.h"
#include "ethApiServer.h"
#include <Arduino.h>
#include <memory>
#ifdef USE_ARDUINO_ETHERNET
#include <Ethernet.h>
#else
#include <RAK13800_W5100S.h>
#endif
// Adaptive poll intervals (mirror mesh/http/WebServer.cpp ESP32 pattern).
static constexpr uint32_t ACTIVE_THRESHOLD_MS = 5000;
static constexpr uint32_t MEDIUM_THRESHOLD_MS = 30000;
static constexpr int32_t ACTIVE_INTERVAL_MS = 20;
static constexpr int32_t MEDIUM_INTERVAL_MS = 100;
static constexpr int32_t IDLE_INTERVAL_MS = 500;
static std::unique_ptr<EthernetServer> apiServer;
// Adapter that exposes an EthernetClient through the transport-agnostic
// IStreamReadWrite interface so the handlers in ethApiHandlers.cpp can drive
// it the same way they drive the TLS transport.
class EthernetClientStream : public IStreamReadWrite
{
public:
explicit EthernetClientStream(EthernetClient &c) : c_(c) {}
size_t write(uint8_t b) override { return c_.write(b); }
size_t write(const uint8_t *buf, size_t len) override { return c_.write(buf, len); }
int available() override { return c_.available(); }
int read() override { return c_.read(); }
int read(uint8_t *buf, size_t len) override { return c_.read(buf, len); }
bool connected() override { return c_.connected(); }
void flush() override { c_.flush(); }
IPAddress remoteIP() override { return c_.remoteIP(); }
private:
EthernetClient &c_;
};
// Dedicated OSThread so accept() runs on sub-second cadence. The Ethernet
// client periodic ticks every 5s which is fine for NTP/MQTT but cripples a
// chatty web client doing many small back-to-back requests (6.5s TTFB observed
// before; W5500 sockets get exhausted faster than the periodic can drain).
class EthApiServerThread : public concurrency::OSThread
{
public:
EthApiServerThread() : concurrency::OSThread("EthApiServer") { lastActivityMs = millis(); }
protected:
int32_t runOnce() override
{
if (apiServer) {
EthernetClient client = apiServer->accept();
if (client) {
lastActivityMs = millis();
EthernetClientStream stream(client);
handleApiClient(stream);
client.stop();
}
}
uint32_t since = millis() - lastActivityMs;
if (since < ACTIVE_THRESHOLD_MS)
return ACTIVE_INTERVAL_MS;
if (since < MEDIUM_THRESHOLD_MS)
return MEDIUM_INTERVAL_MS;
return IDLE_INTERVAL_MS;
}
private:
uint32_t lastActivityMs;
};
static EthApiServerThread *apiThread = nullptr;
void initEthApiServer()
{
// Bind the listener (idempotent - deInitEthApiServer() drops apiServer on a
// W5500 reset, and this rebinds it on the restart path).
if (!apiServer) {
apiServer = std::make_unique<EthernetServer>(ETH_API_PORT);
apiServer->begin();
LOG_INFO("ETH API: server listening on TCP port %d (phase 2.0, OSThread @ 20ms)", ETH_API_PORT);
}
// The worker is created once and kept for the lifetime of the process. It
// idles harmlessly while apiServer is null (runOnce guards on it), so we
// never delete it from another thread's runOnce - that would corrupt the
// scheduler's thread list mid-iteration.
if (!apiThread)
apiThread = new EthApiServerThread(); // OSThread base auto-registers with the scheduler
}
void deInitEthApiServer()
{
// A W5500 chip reset wipes the hardware socket table, so the listener is now
// bound to a dead socket. Drop it (the worker stays alive and idles) so the
// next initEthApiServer() from reconnectETH's restart path rebinds TCP/80.
apiServer.reset();
}
#endif // HAS_ETHERNET && HAS_ETHERNET_API