mirror of
https://github.com/alexhopeoconnor/DFTE.git
synced 2026-10-03 22:21:59 +10:00
Release DFTE v1.2.0
This commit is contained in:
@@ -1,5 +1,10 @@
|
||||
# Changelog
|
||||
|
||||
## 1.2.0
|
||||
|
||||
- Add a borrowed async-response helper for fixed, caller-owned response slots. It avoids a per-request `shared_ptr` control allocation while releasing the slot safely on completion or disconnect.
|
||||
- Add callback-enabled safe shared response helpers so dynamic contexts can return a bounded owner permit at the same time as their shared state is released.
|
||||
|
||||
## 1.1.0
|
||||
|
||||
- Replace layout-affecting compile-time storage overrides with per-context,
|
||||
|
||||
@@ -49,7 +49,7 @@ Build [Hello Placeholder](examples/HelloPlaceholder/) to see the rendered result
|
||||
|
||||
```ini
|
||||
lib_deps =
|
||||
DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.1.0
|
||||
DeviceFrameworkTemplateEngine=https://github.com/alexhopeoconnor/DFTE.git#v1.2.0
|
||||
```
|
||||
|
||||
PlatformIO checks out the Git ref after `#`; GitHub Release assets are unrelated. DFTE supports ESP8266 and ESP32 Arduino projects.
|
||||
|
||||
@@ -24,6 +24,54 @@ void sendTemplate(AsyncWebServerRequest* request, const char* root) {
|
||||
|
||||
The response retains the context while it streams. Releasing the request-owned `shared_ptr` on disconnect prevents state from leaking into later requests.
|
||||
|
||||
## Bounded fixed slots
|
||||
|
||||
Use `beginBorrowedChunkedResponse()` when the application already owns a small, fixed response pool. It does not allocate a `shared_ptr` control block for the request. The release callback must be idempotent because it can run once when rendering finishes and again if the connection later disconnects.
|
||||
|
||||
```cpp
|
||||
struct ResponseSlot {
|
||||
bool busy = false;
|
||||
TemplateContext context;
|
||||
};
|
||||
|
||||
ResponseSlot slots[2];
|
||||
|
||||
void releaseSlot(ResponseSlot& slot) {
|
||||
slot.context.reset();
|
||||
slot.busy = false;
|
||||
}
|
||||
|
||||
void sendBoundedTemplate(AsyncWebServerRequest* request, const char* root) {
|
||||
ResponseSlot* slot = nullptr;
|
||||
for (auto& candidate : slots) {
|
||||
if (!candidate.busy) {
|
||||
slot = &candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (slot == nullptr) {
|
||||
request->send(503, "text/plain", "Busy");
|
||||
return;
|
||||
}
|
||||
|
||||
slot->busy = true;
|
||||
slot->context.setRegistry(registry.get());
|
||||
TemplateRenderer::initializeContext(slot->context, root);
|
||||
request->send(TemplateEngineAsyncWeb::beginBorrowedChunkedResponse(
|
||||
request, "text/html; charset=utf-8", slot,
|
||||
[](ResponseSlot& state, uint8_t* out, size_t size, size_t) {
|
||||
return TemplateEngineAsyncWeb::renderTemplateChunkWithRetries(
|
||||
state.context, out, size, 128);
|
||||
},
|
||||
[](const ResponseSlot& state) {
|
||||
return TemplateEngineAsyncWeb::isTemplateTerminal(state.context);
|
||||
},
|
||||
releaseSlot));
|
||||
}
|
||||
```
|
||||
|
||||
Use this form only while the slot itself has static or otherwise guaranteed lifetime. Use the `shared_ptr` form above for a request-owned dynamic context.
|
||||
|
||||
Use [StreamingAsync](../examples/StreamingAsync/) for a complete SoftAP/captive-portal project.
|
||||
|
||||
Back to [documentation](README.md) · [project overview](../README.md).
|
||||
|
||||
@@ -85,6 +85,103 @@ AsyncWebServerResponse* beginSafeChunkedResponse(AsyncWebServerRequest* request,
|
||||
});
|
||||
}
|
||||
|
||||
// A bounded embedded server can own response state in a fixed slot instead of
|
||||
// allocating a shared control block for every request. The caller guarantees
|
||||
// that state remains valid until release() is called. release() must be safe
|
||||
// to call more than once because a completed request can later disconnect.
|
||||
template <typename StateT, typename FillFn, typename IsDoneFn, typename ReleaseFn>
|
||||
AsyncWebServerResponse* beginBorrowedChunkedResponse(AsyncWebServerRequest* request,
|
||||
const char* contentType,
|
||||
StateT* state,
|
||||
FillFn fill,
|
||||
IsDoneFn isDone,
|
||||
ReleaseFn release) {
|
||||
request->onDisconnect([state, release]() mutable {
|
||||
if (state != nullptr) {
|
||||
release(*state);
|
||||
}
|
||||
});
|
||||
|
||||
return request->beginChunkedResponse(contentType,
|
||||
[state, fill, isDone, release](uint8_t* buffer, size_t maxLen, size_t index) mutable -> size_t {
|
||||
if (state == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (isDone(*state)) {
|
||||
release(*state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (maxLen == 0) {
|
||||
return RESPONSE_TRY_AGAIN;
|
||||
}
|
||||
|
||||
size_t written = fill(*state, buffer, maxLen, index);
|
||||
if (written == RESPONSE_TRY_AGAIN) {
|
||||
return RESPONSE_TRY_AGAIN;
|
||||
}
|
||||
|
||||
if (written > 0) {
|
||||
return written;
|
||||
}
|
||||
|
||||
if (isDone(*state)) {
|
||||
release(*state);
|
||||
return 0;
|
||||
}
|
||||
|
||||
yieldForChunkRetry();
|
||||
return RESPONSE_TRY_AGAIN;
|
||||
});
|
||||
}
|
||||
|
||||
// Variant for dynamically allocated state when the owner also needs an
|
||||
// idempotent completion notification (for example, returning a bounded web
|
||||
// response permit). The original overload intentionally remains available for
|
||||
// callers that only need shared ownership.
|
||||
template <typename StateT, typename FillFn, typename IsDoneFn, typename ReleaseFn>
|
||||
AsyncWebServerResponse* beginSafeChunkedResponse(AsyncWebServerRequest* request,
|
||||
const char* contentType,
|
||||
const std::shared_ptr<StateT>& sharedState,
|
||||
FillFn fill,
|
||||
IsDoneFn isDone,
|
||||
ReleaseFn release) {
|
||||
request->onDisconnect([state = sharedState, release]() mutable {
|
||||
release();
|
||||
state.reset();
|
||||
});
|
||||
|
||||
return request->beginChunkedResponse(contentType,
|
||||
[state = sharedState, fill, isDone, release](uint8_t* buffer, size_t maxLen, size_t index) mutable -> size_t {
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (maxLen == 0) {
|
||||
return RESPONSE_TRY_AGAIN;
|
||||
}
|
||||
|
||||
size_t written = fill(*state, buffer, maxLen, index);
|
||||
if (written == RESPONSE_TRY_AGAIN) {
|
||||
return RESPONSE_TRY_AGAIN;
|
||||
}
|
||||
|
||||
if (written > 0) {
|
||||
return written;
|
||||
}
|
||||
|
||||
if (isDone(*state)) {
|
||||
release();
|
||||
state.reset();
|
||||
return 0;
|
||||
}
|
||||
|
||||
yieldForChunkRetry();
|
||||
return RESPONSE_TRY_AGAIN;
|
||||
});
|
||||
}
|
||||
|
||||
template <typename StateT, typename FillFn, typename IsDoneFn>
|
||||
AsyncWebServerResponse* beginSafeChunkedResponse(AsyncWebServerRequest* request,
|
||||
const String& contentType,
|
||||
@@ -128,6 +225,25 @@ AsyncWebServerResponse* beginSafeChunkedResponse(AsyncWebServerRequest* request,
|
||||
});
|
||||
}
|
||||
|
||||
template <typename ContextT, typename ReleaseFn>
|
||||
AsyncWebServerResponse* beginSafeTemplateResponse(AsyncWebServerRequest* request,
|
||||
const char* contentType,
|
||||
const std::shared_ptr<ContextT>& sharedContext,
|
||||
unsigned maxNoProgressRetries,
|
||||
ReleaseFn release) {
|
||||
return beginSafeChunkedResponse(
|
||||
request,
|
||||
contentType,
|
||||
sharedContext,
|
||||
[maxNoProgressRetries](ContextT& context, uint8_t* buffer, size_t maxLen, size_t /*index*/) -> size_t {
|
||||
return renderTemplateChunkWithRetries(context, buffer, maxLen, maxNoProgressRetries);
|
||||
},
|
||||
[](const ContextT& context) -> bool {
|
||||
return isTemplateTerminal(context);
|
||||
},
|
||||
release);
|
||||
}
|
||||
|
||||
template <typename ContextT>
|
||||
AsyncWebServerResponse* beginSafeTemplateResponse(AsyncWebServerRequest* request,
|
||||
const char* contentType,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "DeviceFrameworkTemplateEngine",
|
||||
"version": "1.1.0",
|
||||
"version": "1.2.0",
|
||||
"description": "Memory-efficient streaming template engine for ESP8266/ESP32 with chunked rendering support. Designed for embedded web interfaces with PROGMEM template support.",
|
||||
"keywords": [
|
||||
"template",
|
||||
|
||||
Reference in New Issue
Block a user