0

I have a fairly good template (as in snippet of code) I pull out whenever I need a singleton class. I am now trying to apply it within my project to allow me to control a single instance of a web server. I can make a web server without encasing it in my class. When I try to encase it within the class I'm apparently too unskilled to pull it off.

I've tried the obvious Googling and searching here. I've read relevant posts. I am sure this does not mean I have a unique problem, just that I've not figured out the right way to fix it. Here's what I am working with:

webserver.h:

#include <ESP8266WebServer.h>
#include <FS.h>

class WebServer {
    private:
        // Singleton Declarations
        static bool instanceFlag;
        static WebServer *single;
        WebServer() {}
        // Other Declarations
        FS *filesystem;
        ESP8266WebServer server();
        String getContentType(String);
        bool handleFileRead(String);

    public:
        // Singleton Declarations
        static WebServer* getInstance();
        ~WebServer() {instanceFlag = false;}
        // Other Declarations
        void initialize(int);
        void handleLoop();
};

webserver.cpp:

#include "webserver.h"

bool WebServer::instanceFlag = false;
WebServer* WebServer::single = NULL;

WebServer* WebServer::getInstance() {
    if(!instanceFlag) {
        single = new WebServer();
        instanceFlag = true;
        return single;
    } else {
        return single;
    }
}

void WebServer::initialize (int port) {
    ESP8266WebServer server(port);
    FS *filesystem;
    filesystem->begin();

    Serial.print("Open: http://");
    Serial.print(WiFi.hostname().c_str());
    Serial.println(".local");

    server.onNotFound([]() {
        if (!single->handleFileRead(single->server.uri())) {
            single->server.send(404, "text/plain", "404: File not found.");
        }
    });

    server.begin();
    Serial.print("HTTP server started on port ");
    Serial.print(port);
    Serial.println(".");
}

String WebServer::getContentType(String filename) {
    if (single->server.hasArg("download")) {
        return "application/octet-stream";
    } else if (filename.endsWith(".htm")) {
        return "text/html";
    } else if (filename.endsWith(".html")) {
        return "text/html";
    } else if (filename.endsWith(".css")) {
        return "text/css";
    } else if (filename.endsWith(".js")) {
        return "application/javascript";
    } else if (filename.endsWith(".png")) {
        return "image/png";
    } else if (filename.endsWith(".gif")) {
        return "image/gif";
    } else if (filename.endsWith(".jpg")) {
        return "image/jpeg";
    } else if (filename.endsWith(".ico")) {
        return "image/x-icon";
    } else if (filename.endsWith(".xml")) {
        return "text/xml";
    } else if (filename.endsWith(".pdf")) {
        return "application/x-pdf";
    } else if (filename.endsWith(".zip")) {
        return "application/x-zip";
    } else if (filename.endsWith(".gz")) {
        return "application/x-gzip";
    } else {
       return "text/plain"; 
    }
}

bool WebServer::handleFileRead(String path) {
    Serial.println("handleFileRead: " + path);
    if (path.endsWith("/")) {
        path += "index.htm";
    }
    String contentType = getContentType(path);
    String pathWithGz = path + ".gz";
    if (filesystem->exists(pathWithGz) || filesystem->exists(path)) {
        if (filesystem->exists(pathWithGz)) {
            path += ".gz";
        }
        File file = filesystem->open(path, "r");
        single->server.streamFile(file, contentType);
        file.close();
        return true;
    }
    return false;
}

void WebServer::handleLoop() {
    single->server.handleClient();
}

The errors I am getting are all similar to the following:

src\webserver.cpp: In member function 'bool WebServer::handleFileRead(String)':
src\webserver.cpp:81:23: error: 'WebServer::single->WebServer::server' does not have class type
         single->server.streamFile(file, contentType);

I get the idea of "does not have a class type", I just have no idea what it means here. In my mind, "single" is a pointer to the class so I'm unclear what that reference is not working.

Obviously, there are ample examples out there how to do a web server without encapsulating it. Other things I need to do for this project lend itself to creating that requirement.

LCB
  • 91
  • 1
  • 11
  • 1
    FWIW, if you are going to make a singleton, you should use a [Meyers' Singleton](https://stackoverflow.com/questions/17712001/how-is-meyers-implementation-of-a-singleton-actually-a-singleton) – NathanOliver Aug 19 '19 at 21:31
  • 1
    `WebServer::server` sure looks a lot like a function: `ESP8266WebServer server();` You sure you want those brackets there, or should we close this as a typo? – user4581301 Aug 19 '19 at 21:42
  • @user4581301, not sure what you mean exactly. The only brackets I see are in the server.onNotFound handler and when I remove that I have the same issue. I'm sure the 'WebServer::single->WebServer::server' part is screaming at me with the information I need, I just don't understand it I guess. – LCB Aug 19 '19 at 23:23
  • Thank you for the reference @NathanOliver. I'll have a look although I suspect I'll make the same mistake instantiating/using the lib class. – LCB Aug 19 '19 at 23:41
  • Apologies for not being clear enough. The brackets at the end of `ESP8266WebServer server();` accidentally declared a function rather than defining a variable, leading to the error message much later. A function is not a class, so you cant use `.` to access a member. One thing you're going to find in programming is that the actual mistake is often nowhere near the crash site. – user4581301 Aug 20 '19 at 02:29
  • Got it, thank you @user4581301. – LCB Aug 20 '19 at 10:47

1 Answers1

3

There are some mistake in your code. In webserver.h:

...
private:
    // Singleton Declarations
    static bool instanceFlag;
    static WebServer *single;
    WebServer() {}
    // Other Declarations
    FS *filesystem;
    ESP8266WebServer *server; // <--- remove the parentheses and make it a pointer
    String getContentType(String);
    bool handleFileRead(String);
...

In webserver.cpp:

In WebServer::initialize I am guessing you want to initialize the class server and filesystem not locals, so it should probably look like this:

void WebServer::initialize (int port) {
    server = new ESP8266WebServer(port);
    filesystem = new FS();
    ...
}

And now everywhere you use the server you have to use the -> operator. For example:

void WebServer::handleLoop() {
    single->server->handleClient();
}

Please keep in mind that server and filesystem objects have to be deleted to avoid memory leaks.

EDIT:

You get the new error because FS has no constructor without arguments. FS's constructor looks like this: FS(FSImplPtr impl) : _impl(impl) { }, here you can see that FSImplPtr is a typedef for std::shared_ptr<FileImpl>, so you need to provide this as a parameter.

It works your way, because SPIFFS's existence is declared here and is of type FS.

If you want to use SPIFFS, you have to use it like this: filesystem = &SPIFFS;, not like you mentioned in the comments (FS* filesystem = &SPIFFS;) because your way creates a new temporary variable named filesystem, and probably you expect to initiate the filesystem in the class, not a local one.

N Alex
  • 1,014
  • 2
  • 18
  • 29
  • Thank you for the concise review of the issues as you see them @N Alex. I'll give that a go. – LCB Aug 19 '19 at 23:43
  • So that was definitely my issue with declaring the ESP8266WebServer - not sure how I overlooked the () there. Maybe if I pay attention to IntelliSense next time? Now getting error in `WebServer::initialize` though: `src\webserver.cpp:18:25: error: no matching function for call to 'fs::FS::FS()'` Not sure what's wrong with the statement `filesystem = new FS();`? I'm sincerely hoping this is not another stupid error which I've made. I changed it to FS* filesystem = &SPIFFS; - testing now to see if that behaves how I expect. – LCB Aug 20 '19 at 00:07
  • So you got me past the error I posted - Thank you! Although I am curious why I need to declare FS as `FS* filesystem = &SPIFFS;` as well as instantiate the same way in `WebServer::initialize`? – LCB Aug 20 '19 at 00:15
  • @LCB please see my edit for an answer to your new error – N Alex Aug 20 '19 at 09:12
  • I understand now, thank you so much for your help @N_Alex. – LCB Aug 20 '19 at 10:55