[Question] Request disconnect callback
In AsyncWebserver we can attach a callback to the request and it gets called after disconnect. This is usefull because if i want to allocate some memory in the request handler i can free it in the disconnect callback
if( request->url() == "/auth/getCurrentUser" ){
char* userJsonString;
if( !alloc(userJsonString,5000) ){ // Allocate a buffer for the json string.
return request->reply(400);
}
request->onDisconnect([userJsonString](){
free(userJsonString); // Free on disconnection so we don't have to deal with it.
});
String sessionID;
if( !request->hasHeader("Cookie") ){
request->send(400);
return;
}
AsyncWebHeader *allCookie = request->getHeader("Cookie");
hshServer.getCookie("sessionID", allCookie->value(), sessionID);
// Rest of the code...
}
Sorry this is half async webserver and half PsychicHttp right now :D
More real world example
Instead of this
if( request->url() == "/auth/getCurrentUser" ){
char* userJsonString;
if( !hshSystem.alloc(userJsonString,USERS_JSON_SIZE) ){
return request->reply(400);
}
if( !request->hasSessionKey("sessionID") ){
free(userJsonString); // Free
return request->reply(400);
}
String sessionID = request->getSessionKey("sessionID");
DynamicJsonDocument userDoc(USERS_JSON_SIZE);
JsonObject userObj = userDoc.to<JsonObject>();
if( !user.getUserBySession(sessionID.c_str(),userObj) ){
free(userJsonString); // Every
return request->reply(400);
}
if( serializeJson(userObj,userJsonString,USERS_JSON_SIZE) < 1 ){
free(userJsonString); // single
return request->reply(400);
}
esp_err_t result = request->reply(200,"application/json",userJsonString);
free(userJsonString); // time
return result;
}
It would became this
if( request->url() == "/auth/getCurrentUser" ){
char* userJsonString;
if( !hshSystem.alloc(userJsonString,USERS_JSON_SIZE) ){
return request->reply(400);
}
request->onDisconnect([userJsonString](){
free(userJsonString); // Free after disconnection
});
if( !request->hasSessionKey("sessionID") ){
return request->reply(400);
}
String sessionID = request->getSessionKey("sessionID");
DynamicJsonDocument userDoc(USERS_JSON_SIZE);
JsonObject userObj = userDoc.to<JsonObject>();
if( !user.getUserBySession(sessionID.c_str(),userObj) ){
return request->reply(400);
}
if( serializeJson(userObj,userJsonString,USERS_JSON_SIZE) < 1 ){
return request->reply(400);
}
return request->reply(200,"application/json",userJsonString);
}
I can definitely add this. I had support for it partially added, but wasn't sure that it was useful or not. I will re-add that back in and let you know.
I'm looking at implementing this now, but the way its done with websockets and eventsource, the client list is kept in the handler object itself. If I was to implement it for any generic handler, then you'd end up with new sessions getting started in each handler.
Have you seen the server.onOpen and onClose callbacks? If you're doing sessions, that would probably be the place to put it.
Actually i dont use sessions. I have an spa. I wanted to use it for user login time but i get back to using cookies.
But i see what you mean
I have a few old Async* request->onDisconnect()'s I like to port over. Is there a standard way of doing this now (not ES or WS).
Couldn't sleep, added in the callbacks:
//client connect/disconnect to a url
// curl -i http://psychic.local/handler
PsychicWebHandler *connectionHandler = new PsychicWebHandler();
connectionHandler->onRequest([](PsychicRequest *request)
{
return request->reply("OK");
});
connectionHandler->onOpen([](PsychicClient *client) {
Serial.printf("[handler] connection #%u connected from %s\n", client->socket(), client->remoteIP().toString());
});
connectionHandler->onClose([](PsychicClient *client) {
Serial.printf("[handler] connection #%u closed from %s\n", client->socket(), client->remoteIP().toString());
});
Its worth noting that it will only track clients to that specific url, so if you need to do something globally, use the server.onOpen and server.onClose instead
Really nice! Maybe this will let us defer the requests too? :O
Oh this is not what i was expecting. I expected the disconnect callback on the request object
server.on("/test", HTTP_GET, [](PsychicRequest *request){
request->onDisconnect([](){
// Handle something on request disconnection.
});
// Handle the request...
});
@zekageri unfortunately getting a disconnect event on requests is unlikely to happen for a couple reasons:
- ESP-IDF only provides a connection open/close event at the server level, there is really no way to associate it with a request. best we can do is track what clients belong to what handlers and pass the event to the handler
- a request doesnt always end with a disconnection. most modern browsers will keep the connection open for further requests.
That actually makes a lot of sense
Oh. Excuse my dumbness but i dont understand how should i add the handler to a url. If I manage to add this dc handler to a specific url somehow isnt it the same?
Doesnt the server free the socket on response? If I allocate some memory in the handler, when should i free it if the socket never freed?
Oh. Excuse my dumbness but i dont understand how should i add the handler to a url. If I manage to add this dc handler to a specific url somehow isnt it the same?
The basic flow goes like this:
server.onOpen(client) handler.onOpen(client) handler.onRequest(request) //potentially multiple requests from client handler.onClose(client) server.onClose(client)
If you need to allocate memory for a user session you can do it in the onOpen and free it in the onClose. Most likely you want it on a per-server basis, but there is the options for a per-handler basis too.
Doesnt the server free the socket on response? If I allocate some memory in the handler, when should i free it if the socket never freed?
No, the server frees all the socket memory when the connection is closed, either normally or if there is an error. That connection can stay open for multiple requests. I would avoid allocating memory in the onRequest callback unless you are freeing it within that same function. If you really need to, use the onOpen() and onClose()
@hoeken I have a similar use case to @zekageri 's.
I have a 'Restart' button on a web page that reboots the ESP32 but I want to gracefully exit from the response first before the backend code calls ESP.restart();.
In AsyncWS this could be done using request->onDisconnect() as a post-action, to make the restart call.
Can you think of nifty way to do something similar with PsychicHttp?
My problem is the following. In the request cb i allocate memory in ext ram. I need a huge chunk. I create a json document and fill it with data. I serialize the doc to the allocated char array and let the server send it. Since it is a large array the server will chunk it and send it chunked. Since i cant wait for it in the cb because i have to return immediately i can't free the memory while its sending. So I need a dc cb to be able to free the memory when this large array sent.
At least that was the case with the previous lib. I also did a lot of deferred request and used disconnect callbacks because i had huge responses
Huge for the esp at least
@proddy @zekageri I hear what you guys are saying and am trying to figure out how to make it work. There are a couple things at play here, the main one being that we only really have 3 points of contact with the server: the onOpen, onRequest, and onClose callback stuff provided by ESP-IDF http server.
@proddy have you tried calling the request->reply() or response->send(), storing the return value, then doing a reboot? the response->send() function directly sends the data over tcp, so it should be okay for the server to reboot after that. you might even be able to do something like response->client()->close() to make it explicit, or even loop over all the server clients and call close on each one. The return from the onRequest function is mostly just to let the ESP-HTTP server know if handling the request was successful or not (and closes the connection on anything other than ESP_OK).
@zekageri - i still don't have a good answer for how to handle your deferred responses. i should really modify the main page and add an asterisk to the multithreading part. The whole server runs in its own thread, but processes requests one at a time. in 5.1.2 there is support for true multithreaded processing where each request gets handled in its own thread. that will let you just do your long request in one callback. I tried backporting the changes from 5.1.2, but I couldn't get it to work. You can see my attempt in the async_worker.h and async_worker.cpp files. You can enable it by defining ENABLE_ASYNC. I'm a bit out of my depth with the underlying freertos threading stuff so maybe i missed something in the backport.
thanks @hoeken - it was a problem with my code in the end (always is!). I had
request->reply(200);
WiFi.disconnect(true); <----- removed this line
delay(500);
ESP.restart();
The wifi disconnect was too quick and killed the connection before the httpd reply could be sent. Removing the Wifi.disconnect fixed it.
So this type of dc cb will never happen?
server.on("/test", HTTP_GET, [](PsychicRequest *request){
request->onDisconnect([](){
// Handle something on request disconnection.
});
// Handle the request...
})
It gets called when that client closes it's connection to the server. You can test it with curl which should open and close it all in one go, but if you do it with a web browser they tend to keep connections open for a while in case there are more requests so it is delayed.
On Thu, Jan 4, 2024, 09:15 DrRandom @.***> wrote:
So this type of dc cb will never happen?
server.on("/test", HTTP_GET, [](PsychicRequest *request){ request->onDisconnect({ // Handle something on request disconnection. }); // Handle the request... })
— Reply to this email directly, view it on GitHub https://github.com/hoeken/PsychicHttp/issues/12#issuecomment-1877162235, or unsubscribe https://github.com/notifications/unsubscribe-auth/AABEHSQ6YEUTLPEFAG5RRDTYM22Q7AVCNFSM6AAAAABAPKPBZSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTQNZXGE3DEMRTGU . You are receiving this because you were mentioned.Message ID: @.***>
I'm closing all these old issues. Please re-open it if needed.