Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions examples/RequestContinuation/RequestContinuation.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov

//
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
//

#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif

#include <ESPAsyncWebServer.h>
#include <list>
#include <mutex>

static AsyncWebServer server(80);

// request handler that is saved from the paused request to communicate with Serial
static String message;
static AsyncWebServerRequestPtr serialRequest;

// request handler that is saved from the paused request to communicate with GPIO
static uint8_t pin = 35;
static AsyncWebServerRequestPtr gpioRequest;

void setup() {
Serial.begin(115200);

#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

// Post a message that will be sent to the Serial console, and pause the request until the user types a key
//
// curl -v -X POST -H "Content-Type: application/x-www-form-urlencoded" -d "question=Name%3F%20" http://192.168.4.1/serial
//
// curl output should show "Answer: [y/n]" as the response
server.on("/serial", HTTP_POST, [](AsyncWebServerRequest *request) {
message = request->getParam("question", true)->value();
serialRequest = request->pause();
});

// Wait for a GPIO to be high
//
// curl -v http://192.168.4.1/gpio
//
// curl output should show "GPIO is high!" as the response
server.on("/gpio", HTTP_GET, [](AsyncWebServerRequest *request) {
gpioRequest = request->pause();
});

pinMode(pin, INPUT);

server.begin();
}

void loop() {
delay(500);

// Check for a high voltage on the RX1 pin
if (digitalRead(pin) == HIGH) {
if (auto request = gpioRequest.lock()) {
request->send(200, "text/plain", "GPIO is high!");
}
}

// check for an incoming message from the Serial console
if (message.length()) {
Serial.printf("%s", message.c_str());
// drops buffer
while (Serial.available()) {
Serial.read();
}
Serial.setTimeout(10000);
String response = Serial.readStringUntil('\n'); // waits for a key to be pressed
Serial.println();
message = emptyString;
if (auto request = serialRequest.lock()) {
request->send(200, "text/plain", "Answer: " + response);
}
}
}
165 changes: 165 additions & 0 deletions examples/RequestContinuationComplete/RequestContinuationComplete.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov

//
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
//

#include <Arduino.h>
#ifdef ESP32
#include <AsyncTCP.h>
#include <WiFi.h>
#elif defined(ESP8266)
#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#elif defined(TARGET_RP2040)
#include <WebServer.h>
#include <WiFi.h>
#endif

#include <ESPAsyncWebServer.h>

#include <list>
#include <memory>
#include <mutex>

static AsyncWebServer server(80);

// ===============================================================
// The code below is used to simulate some long running operations
// ===============================================================

typedef struct {
size_t id;
AsyncWebServerRequestPtr requestPtr;
uint8_t data;
} LongRunningOperation;

static std::list<std::unique_ptr<LongRunningOperation>> longRunningOperations;
static size_t longRunningOperationsCount = 0;
#ifdef ESP32
static std::mutex longRunningOperationsMutex;
#endif

static void startLongRunningOperation(AsyncWebServerRequestPtr &&requestPtr) {
#ifdef ESP32
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
#endif

// LongRunningOperation *op = new LongRunningOperation();
std::unique_ptr<LongRunningOperation> op(new LongRunningOperation());
op->id = ++longRunningOperationsCount;
op->data = 10;

// you need to hold the AsyncWebServerRequestPtr returned by pause();
// This object is authorized to leave the scope of the request handler.
op->requestPtr = std::move(requestPtr);

Serial.printf("[%u] Start long running operation for %" PRIu8 " seconds...\n", op->id, op->data);
longRunningOperations.push_back(std::move(op));
}

static bool processLongRunningOperation(LongRunningOperation *op) {
// request was deleted ?
if (op->requestPtr.expired()) {
Serial.printf("[%u] Request was deleted - stopping long running operation\n", op->id);
return true; // operation finished
}

// processing the operation
Serial.printf("[%u] Long running operation processing... %" PRIu8 " seconds left\n", op->id, op->data);

// check if we have finished ?
op->data--;
if (op->data) {
// not finished yet
return false;
}

// Try to get access to the request pointer if it is still exist.
// If there has been a disconnection during that time, the pointer won't be valid anymore
if (auto request = op->requestPtr.lock()) {
Serial.printf("[%u] Long running operation finished! Sending back response...\n", op->id);
request->send(200, "text/plain", String(op->id) + " ");

} else {
Serial.printf("[%u] Long running operation finished, but request was deleted!\n", op->id);
}

return true; // operation finished
}

/// ==========================================================

void setup() {
Serial.begin(115200);

#ifndef CONFIG_IDF_TARGET_ESP32H2
WiFi.mode(WIFI_AP);
WiFi.softAP("esp-captive");
#endif

// Add a middleware to see how pausing a request affects the middleware chain
server.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
Serial.printf("Middleware chain start\n");

// continue to the next middleware, and at the end the request handler
next();

// we can check the request pause state after the handler was executed
if (request->isPaused()) {
Serial.printf("Request was paused!\n");
}

Serial.printf("Middleware chain ends\n");
});

// HOW TO RUN THIS EXAMPLE:
//
// 1. Open several terminals to trigger some requests concurrently that will be paused with:
// > time curl -v http://192.168.4.1/
//
// 2. Look at the output of the Serial console to see how the middleware chain is executed
// and to see the long running operations being processed and resume the requests.
//
// 3. You can try close your curl command to cancel the request and check that the request is deleted.
// Note: in case the network is disconnected, the request will be deleted.
//
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
// Print a message in case the request is disconnected (network disconnection, client close, etc.)
request->onDisconnect([]() {
Serial.printf("Request was disconnected!\n");
});

// Instruct ESPAsyncWebServer to pause the request and get a AsyncWebServerRequestPtr to be able to access the request later.
// The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler.
// The Middleware chain will continue to run until the end after this handler exit, but the request will be paused and will not
// be sent to the client until send() is called later.
Serial.printf("Pausing request...\n");
AsyncWebServerRequestPtr requestPtr = request->pause();

// start our long operation...
startLongRunningOperation(std::move(requestPtr));
});

server.begin();
}

static uint32_t lastTime = 0;

void loop() {
if (millis() - lastTime >= 1000) {

#ifdef ESP32
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
#endif

// process all long running operations
longRunningOperations.remove_if([](const std::unique_ptr<LongRunningOperation> &op) {
return processLongRunningOperation(op.get());
});

lastTime = millis();
}
}
2 changes: 2 additions & 0 deletions platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ lib_dir = .
src_dir = examples/PerfTests
; src_dir = examples/RateLimit
; src_dir = examples/Redirect
; src_dir = examples/RequestContinuation
; src_dir = examples/RequestContinuationComplete
; src_dir = examples/ResumableDownload
; src_dir = examples/Rewrite
; src_dir = examples/ServerSentEvents
Expand Down
22 changes: 20 additions & 2 deletions src/ESPAsyncWebServer.h
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ typedef enum {
typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller;
typedef std::function<String(const String &)> AwsTemplateProcessor;

using AsyncWebServerRequestPtr = std::weak_ptr<AsyncWebServerRequest>;

class AsyncWebServerRequest {
using File = fs::File;
using FS = fs::FS;
Expand All @@ -192,8 +194,9 @@ class AsyncWebServerRequest {
AsyncWebServerResponse *_response;
ArDisconnectHandler _onDisconnectfn;

// response is sent
bool _sent = false;
bool _sent = false; // response is sent
bool _paused = false; // request is paused (request continuation)
std::shared_ptr<AsyncWebServerRequest> _this; // shared pointer to this request

String _temp;
uint8_t _parseState;
Expand Down Expand Up @@ -484,6 +487,21 @@ class AsyncWebServerRequest {
#endif
AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);

/**
* @brief Request Continuation: this function pauses the current request and returns a weak pointer (AsyncWebServerRequestPtr is a std::weak_ptr) to the request in order to reuse it later on.
* The middelware chain will continue to be processed until the end, but no response will be sent.
* To resume operations (send the request), the request must be retrieved from the weak pointer and a send() function must be called.
* AsyncWebServerRequestPtr is the only object allowed to exist the scope of the request handler.
* @warning This function should be called from within the context of a request (in a handler or middleware for example).
* @warning While the request is paused, if the client aborts the request, the latter will be disconnected and deleted.
* So it is the responsibility of the user to check the validity of the request pointer (AsyncWebServerRequestPtr) before using it by calling lock() and/or expired().
*/
AsyncWebServerRequestPtr pause();

bool isPaused() const {
return _paused;
}

/**
* @brief Get the Request parameter by name
*
Expand Down
27 changes: 26 additions & 1 deletion src/WebRequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#define __is_param_char(c) ((c) && ((c) != '{') && ((c) != '[') && ((c) != '&') && ((c) != '='))

static void doNotDelete(AsyncWebServerRequest *) {}

using namespace asyncsrv;

enum {
Expand Down Expand Up @@ -75,6 +77,8 @@ AsyncWebServerRequest::AsyncWebServerRequest(AsyncWebServer *s, AsyncClient *c)
}

AsyncWebServerRequest::~AsyncWebServerRequest() {
_this.reset();

_headers.clear();

_pathParams.clear();
Expand Down Expand Up @@ -691,7 +695,7 @@ void AsyncWebServerRequest::_runMiddlewareChain() {
}

void AsyncWebServerRequest::_send() {
if (!_sent) {
if (!_sent && !_paused) {
// log_d("AsyncWebServerRequest::_send()");

// user did not create a response ?
Expand All @@ -711,6 +715,18 @@ void AsyncWebServerRequest::_send() {
}
}

AsyncWebServerRequestPtr AsyncWebServerRequest::pause() {
if (_paused) {
return _this;
}
client()->setRxTimeout(0);
// this shared ptr will hold the request pointer until it gets destroyed following a disconnect.
// this is just used as a holder providing weak observers, so the deleter is a no-op.
_this = std::shared_ptr<AsyncWebServerRequest>(this, doNotDelete);
_paused = true;
return _this;
}

size_t AsyncWebServerRequest::headers() const {
return _headers.size();
}
Expand Down Expand Up @@ -887,13 +903,22 @@ AsyncWebServerResponse *AsyncWebServerRequest::beginResponse_P(int code, const S
}

void AsyncWebServerRequest::send(AsyncWebServerResponse *response) {
// request is already sent on the wire ?
if (_sent) {
return;
}

// if we already had a response, delete it and replace it with the new one
if (_response) {
delete _response;
}
_response = response;

// if request was paused, we need to send the response now
if (_paused) {
_paused = false;
_send();
}
}

void AsyncWebServerRequest::redirect(const char *url, int code) {
Expand Down