Skip to content

Commit c638175

Browse files
committed
Request Continuation support
**Request Continuation** is the ability to pause the processing of a request (the actual sending over the network) to be able to let another task commit the response on the network later. This is a common supported use case amongst web servers. A uage example is described in the following discussion: #34 Currently, the only supported way is to use chunk encoding and return `RESPONSE_TRY_AGAIN` from the callback until the processing is done somewhere else, then send the response in the chunk buffer, when the processing is completed.
1 parent 1b29f18 commit c638175

File tree

5 files changed

+292
-38
lines changed

5 files changed

+292
-38
lines changed

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,52 @@ myHandler.addMiddleware(&authMiddleware); // add authentication to a specific ha
370370
These callbacks can be called multiple times during request parsing, so this is up to the user to now call the `AsyncAuthenticationMiddleware.allowed(request)` if needed and ideally when the method is called for the first time.
371371
These callbacks are also not triggering the whole middleware chain since they are not part of the request processing workflow (they are not the final handler).
372372

373+
## Request Continuation
374+
375+
Request Continuation is the ability to pause the processing of a request (the actual sending over the network) to be able to let another task commit the response on the network later.
376+
377+
This is a common supported use case amongst web servers.
378+
379+
A usage example can be found in the example called [RequestContinuation.ino](https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/examples/RequestContinuation/RequestContinuation.ino)
380+
381+
In the handler receiving the request, just excute:
382+
383+
```c++
384+
AsyncWebServerRequestPtr ptr = request->pause();
385+
```
386+
387+
This will pause the request and return a `AsyncWebServerRequestPtr` (this is a weak pointer).
388+
389+
**The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler.**
390+
391+
Save somewhere this pointer and use it later to commit the response:
392+
393+
```c++
394+
// you can check for expiration
395+
if (requestPtr.expired()) {
396+
// the request connection was closed some time ago so the request is not accessible anymore
397+
398+
} else if (longRunningTaskFinished) {
399+
// this is what you always need to do when you want to access the request.
400+
if (auto request = requestPtr.lock()) {
401+
// send back the response
402+
request->send(200, contentType, ...);
403+
404+
} else {
405+
// the connection has been closed so the request is not accessible anymore
406+
}
407+
}
408+
```
409+
410+
Most of the time you can simply it has:
411+
412+
```c++
413+
if (auto request = requestPtr.lock()) {
414+
// send back the response
415+
request->send(200, contentType, ...);
416+
}
417+
```
418+
373419
## Original Documentation
374420

375421
<!-- no toc -->
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
// SPDX-License-Identifier: LGPL-3.0-or-later
2+
// Copyright 2016-2025 Hristo Gochkov, Mathieu Carbou, Emil Muratov
3+
4+
//
5+
// Shows how to use request continuation to pause a request for a long processing task, and be able to resume it later.
6+
//
7+
8+
#include <Arduino.h>
9+
#ifdef ESP32
10+
#include <AsyncTCP.h>
11+
#include <WiFi.h>
12+
#elif defined(ESP8266)
13+
#include <ESP8266WiFi.h>
14+
#include <ESPAsyncTCP.h>
15+
#elif defined(TARGET_RP2040)
16+
#include <WebServer.h>
17+
#include <WiFi.h>
18+
#endif
19+
20+
#include <ESPAsyncWebServer.h>
21+
#include <list>
22+
#include <mutex>
23+
24+
static AsyncWebServer server(80);
25+
26+
// ===============================================================
27+
// The code below is used to simulate some long running operations
28+
// ===============================================================
29+
30+
typedef struct {
31+
size_t id;
32+
AsyncWebServerRequestPtr requestPtr;
33+
uint8_t data;
34+
} LongRunningOperation;
35+
36+
static std::list<LongRunningOperation *> longRunningOperations;
37+
static size_t longRunningOperationsCount = 0;
38+
static std::mutex longRunningOperationsMutex;
39+
40+
static void startLongRunningOperation(AsyncWebServerRequestPtr requestPtr) {
41+
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
42+
43+
LongRunningOperation *op = new LongRunningOperation();
44+
op->id = ++longRunningOperationsCount;
45+
op->data = random(5, 20);
46+
47+
// you need to hold the AsyncWebServerRequestPtr returned by pause();
48+
// This object is authorized to leave the scope of the request handler.
49+
op->requestPtr = requestPtr;
50+
51+
Serial.printf("[%u] Start long running operation for %" PRIu8 " seconds...\n", op->id, op->data);
52+
longRunningOperations.push_back(op);
53+
}
54+
55+
static bool processLongRunningOperation(LongRunningOperation *op) {
56+
// request was deleted ?
57+
if (op->requestPtr.expired()) {
58+
Serial.printf("[%u] Request was deleted - stopping long running operation\n", op->id);
59+
return true; // operation finished
60+
}
61+
62+
// processing the operation
63+
Serial.printf("[%u] Long running operation processing... %" PRIu8 " seconds left\n", op->id, op->data);
64+
65+
// check if we have finished ?
66+
op->data--;
67+
if (op->data) {
68+
// not finished yet
69+
return false;
70+
}
71+
72+
// Try to get access to the request pointer if it is still exist.
73+
// If there has been a disconnection during that time, the pointer won't be valid anymore
74+
if (auto request = op->requestPtr.lock()) {
75+
Serial.printf("[%u] Long running operation finished! Sending back response...\n", op->id);
76+
request->send(200, "text/plain", String("Long running operation ") + op->id + " finished.");
77+
78+
} else {
79+
Serial.printf("[%u] Long running operation finished, but request was deleted!\n", op->id);
80+
}
81+
82+
return true; // operation finished
83+
}
84+
85+
/// ==========================================================
86+
87+
void setup() {
88+
Serial.begin(115200);
89+
90+
#ifndef CONFIG_IDF_TARGET_ESP32H2
91+
WiFi.mode(WIFI_AP);
92+
WiFi.softAP("esp-captive");
93+
#endif
94+
95+
// Add a middleware to see how pausing a request affects the middleware chain
96+
server.addMiddleware([](AsyncWebServerRequest *request, ArMiddlewareNext next) {
97+
Serial.printf("Middleware chain start\n");
98+
99+
// continue to the next middleware, and at the end the request handler
100+
next();
101+
102+
// we can check the request pause state after the handler was executed
103+
if (request->isPaused()) {
104+
Serial.printf("Request was paused!\n");
105+
}
106+
107+
Serial.printf("Middleware chain ends\n");
108+
});
109+
110+
// HOW TO RUN THIS EXAMPLE:
111+
//
112+
// 1. Open several terminals to trigger some requests concurrently that will be paused with:
113+
// > time curl -v http://192.168.4.1/
114+
//
115+
// 2. Look at the output of the Serial console to see how the middleware chain is executed
116+
// and to see the long running operations being processed and resume the requests.
117+
//
118+
// 3. You can try close your curl comand to cancel the request and check that the request is deleted.
119+
// Note: in case the network is disconnected, the request will be deleted.
120+
//
121+
server.on("/", HTTP_GET, [](AsyncWebServerRequest *request) {
122+
// Print a message in case the request is disconnected (network disconnection, client close, etc.)
123+
request->onDisconnect([]() {
124+
Serial.printf("Request was disconnected!\n");
125+
});
126+
127+
// Instruct ESPAsyncWebServer to pause the request and get a AsyncWebServerRequestPtr to be able to access the request later.
128+
// The AsyncWebServerRequestPtr is the ONLY object authorized to leave the scope of the request handler.
129+
// The Middleware chain will continue to run until the end after this handler exit, but the request will be paused and will not
130+
// be sent to the client until send() is called later.
131+
Serial.printf("Pausing request...\n");
132+
AsyncWebServerRequestPtr ptr = request->pause();
133+
134+
// start our long operation...
135+
startLongRunningOperation(ptr);
136+
});
137+
138+
server.begin();
139+
}
140+
141+
static uint32_t lastTime = 0;
142+
143+
void loop() {
144+
if (millis() - lastTime >= 1000) {
145+
146+
#ifdef ESP32
147+
Serial.printf("Free heap: %" PRIu32 "\n", ESP.getFreeHeap());
148+
#endif
149+
150+
std::lock_guard<std::mutex> lock(longRunningOperationsMutex);
151+
152+
// process all long running operations
153+
std::list<LongRunningOperation *> finished;
154+
for (auto it = longRunningOperations.begin(); it != longRunningOperations.end(); ++it) {
155+
bool done = processLongRunningOperation(*it);
156+
if (done) {
157+
finished.push_back(*it);
158+
}
159+
}
160+
161+
// remove finished operations
162+
for (LongRunningOperation *op : finished) {
163+
Serial.printf("[%u] Deleting finished long running operation\n", op->id);
164+
longRunningOperations.remove(op);
165+
delete op;
166+
}
167+
168+
lastTime = millis();
169+
}
170+
}

platformio.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ lib_dir = .
1919
src_dir = examples/PerfTests
2020
; src_dir = examples/RateLimit
2121
; src_dir = examples/Redirect
22+
; src_dir = examples/RequestContinuation
2223
; src_dir = examples/ResumableDownload
2324
; src_dir = examples/Rewrite
2425
; src_dir = examples/ServerSentEvents

src/ESPAsyncWebServer.h

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,8 @@ typedef enum {
179179
typedef std::function<size_t(uint8_t *, size_t, size_t)> AwsResponseFiller;
180180
typedef std::function<String(const String &)> AwsTemplateProcessor;
181181

182+
using AsyncWebServerRequestPtr = std::weak_ptr<AsyncWebServerRequest>;
183+
182184
class AsyncWebServerRequest {
183185
using File = fs::File;
184186
using FS = fs::FS;
@@ -192,8 +194,9 @@ class AsyncWebServerRequest {
192194
AsyncWebServerResponse *_response;
193195
ArDisconnectHandler _onDisconnectfn;
194196

195-
// response is sent
196-
bool _sent = false;
197+
bool _sent = false; // response is sent
198+
bool _paused = false; // request is paused (request continuation)
199+
std::shared_ptr<AsyncWebServerRequest> _this; // shared pointer to this request
197200

198201
String _temp;
199202
uint8_t _parseState;
@@ -251,6 +254,8 @@ class AsyncWebServerRequest {
251254
void _handleUploadByte(uint8_t data, bool last);
252255
void _handleUploadEnd();
253256

257+
void _send();
258+
254259
public:
255260
File _tempFile;
256261
void *_tempObject;
@@ -478,6 +483,20 @@ class AsyncWebServerRequest {
478483
#endif
479484
AsyncWebServerResponse *beginResponse_P(int code, const String &contentType, PGM_P content, AwsTemplateProcessor callback = nullptr);
480485

486+
/**
487+
* @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.
488+
* The middelware chain will continue to be processed until the end, but no response will be sent.
489+
* To resume operations (send the request), the request must be retrieved from the weak pointer and a send() function must be called.
490+
* @warning This function should be called from within the context of a request (in a handler or middleware for example).
491+
* @warning While the request is paused, any network disconnection will cause the request to be deleted.
492+
* 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().
493+
*/
494+
AsyncWebServerRequestPtr pause();
495+
496+
bool isPaused() const {
497+
return _paused;
498+
}
499+
481500
/**
482501
* @brief Get the Request parameter by name
483502
*

0 commit comments

Comments
 (0)