Arduino: WebServer callback context

Created on 2 Mar 2016  Â·  7Comments  Â·  Source: esp8266/Arduino

Hi,

Here's a snippet from ESP8266WebServer.h:

typedef std::function<void(void)> THandlerFunction;
void on(const char* uri, THandlerFunction handler);
void on(const char* uri, HTTPMethod method, THandlerFunction fn);
void on(const char* uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn);

Is there a reason why it's not defined like this instead:
typedef std::function<void(void*)> THandlerFunction
void on(const char* uri, THandlerFunction handler, void *user_ctx);
void on(const char* uri, HTTPMethod method, THandlerFunction fn, void *user_ctx);
void on(const char* uri, HTTPMethod method, THandlerFunction fn, THandlerFunction ufn, void *user_ctx);

If it was defined this way, we could register a user context variable along with the handler function.
At the moment, if I have a context I'd like to use in the handler function, I must put the context into a global variable (do you agree?). I don't like using globals if I don't have to.

What do you guys think?

question waiting for feedback

Most helpful comment

I think void* user_ctx is so very C.
How about doing what you need in a C++-way?

void handler(SomeParam param)
{
}
// later...
SomeParam myParam;
server.on("/", HTTP_GET, std::bind(&handler, myParam));

or better yet:

class MyClass {
public:
    MyClass(ESP8266WebServer& server) : m_server(server) 
    {
    }

    void begin(const char* uri) 
    {
        m_server.on(uri, HTTP_GET, std::bind(&MyClass::handler, this));
    }

protected:
    void handler() 
    {
        // do something
    }
    ESP8266WebServer& m_server;
};

All 7 comments

I think void* user_ctx is so very C.
How about doing what you need in a C++-way?

void handler(SomeParam param)
{
}
// later...
SomeParam myParam;
server.on("/", HTTP_GET, std::bind(&handler, myParam));

or better yet:

class MyClass {
public:
    MyClass(ESP8266WebServer& server) : m_server(server) 
    {
    }

    void begin(const char* uri) 
    {
        m_server.on(uri, HTTP_GET, std::bind(&MyClass::handler, this));
    }

protected:
    void handler() 
    {
        // do something
    }
    ESP8266WebServer& m_server;
};

@ronlivne are you okay with this solution? Can we close this?

Hi,

Yeah, I'm fine with it.

Thanks,
Ron

On Fri, Mar 11, 2016 at 4:13 PM, Ivan Grokhotkov [email protected]
wrote:

@ronlivne https://github.com/ronlivne are you okay with this solution?
Can we close this?

—
Reply to this email directly or view it on GitHub
https://github.com/esp8266/Arduino/issues/1711#issuecomment-195381891.

@igrr thanks for the first hint to solve at least one problem I have.
So this now works fine for me:

server.on("/login", std::bind(&EspDevice::handleLogin, this));

Do you also have a solution for the other function for use within own classes:

void ESP8266WebServer::on(const char* uri, HTTPMethod method, ESP8266WebServer::THandlerFunction fn, ESP8266WebServer::THandlerFunction ufn) { ...
server.on("/edit", HTTP_POST, [](){ server.send(200, "text/plain", ""); }, handleFileUpload);

this doesn't compile:

    server.on("/edit", HTTP_POST, 
        [](){ server.send(200, "text/plain", ""); }, 
        std::bind(&EspDevice::handleFileUpload, this));

nor this:

    server.on("/edit", HTTP_POST, 
        std::bind(&EspDevice::renderPage, this), 
        std::bind(&EspDevice::handleFileUpload, this));

with

    void EspDevice::renderPage(){
        server.send(200, "text/plain", "");
    }

Is to late but the solution is put in the second argument '&'
server.on("/edit", HTTP_POST, [ & ](){ server.send(200, "text/plain", ""); }, handleFileUpload);

This issue was closed based on the acceptance of the proposed solution. However, I was not able to make this proposed solution work:
server.on("/", HTTP_GET, std::bind(&handler, myParam));
or using alternative notation
server.on("/", HTTP_ANY, [this](MyParam *myParam) { std::bind(&handleSitemap, myParam);));
using the master branch version.

error: 'myParam' is not captured

Looking at source code, it looks like the capability was not (yet) implemented. Did I miss something?
Thanks in advance.

My bad, lack of c++ 11 skills. It is 100% working on both 2.5.2 and git master. If your callback function is part of a class, it requires the proper std::bind syntax:

struct Sitemap {
    const char *name;
};
void MyClass::handleSitemap(Sitemap* sm) {
    DbgPrintln("Sitemap name", sm->name);   
}
Sitemap sm;
sm.name = "test";
server.on("/rest/sitemaps", std::bind(&MyClass::handleSitemap, this, &sm));

When defined outside of a class, one can use a simpler notation:

std::bind(handleSitemap, &sm)

but inside a class, the function address of _handleSitemap_ is relative to the _MyClass_ instance referred to by _this_.
Another simple way to test the call back code is using:

auto fn = bind(&MyClass ::handleSitemap, this, &sm);
fn();

which is the equivalent of calling

MyClass myclass;
myclass.handleSitemap(&sm);

but represented by a variable _fn_ of type "_std::function

Was this page helpful?
0 / 5 - 0 ratings

Related issues

mreschka picture mreschka  Â·  3Comments

mechanic98 picture mechanic98  Â·  3Comments

hoacvxd picture hoacvxd  Â·  3Comments

hulkco picture hulkco  Â·  3Comments

pablotix20 picture pablotix20  Â·  3Comments