dhbaird / easywsclient

A short and sweet WebSocket client for C++
MIT License
751 stars 204 forks source link

Make easywsclient injectable #31

Open dhbaird opened 10 years ago

dhbaird commented 10 years ago

Rather than have easywsclient depend directly on a specific socket implementation, instead inject the socket implementation. Reasons why this is needed:

  1. Platform support (currently resolved by using macros). Dependency injection enables more platform support without needing to resorting to macro flags. Right now, Winsock vs UNIX sockets, is determined with some macro craziness. Further, adding another layer to support SSL gets even more macro crazy. Let's just throw the macros out (mostly) and do dependency injection instead. (I don't mean to diss on macros! But this case here is not a good use of macros.)
  2. SSL (would resolve #9 and #29). Dependency injection makes it possible to inject (or not) an SSL implementation. This means that the user will have the flexibility to choose OpenSSL or Mozilla NSS or anything else they want.
  3. Unit testing. Dependency injection would make it possible to formally unit test the easywsclient code itself.

Here's some pseudocode along these lines...

class Socket // just a normal TCP socket, not a websocket. This is what gets injected.
{
    // return codes follow the BSD/POSIX convention:
    enum { EWOULDBLOCK = ..., ... };
    virtual void close() = 0;
    virtual ssize_t recv(void *buf, size_t len) = 0;
    virtual ssize_t send(const void *buf, size_t len) = 0;
    virtual void select(int millisecondsTimeout) = 0;
};

class RealWebSocket : public easywsclient::WebSocket
{
    // Now inject a Socket object instead of a socket file descriptor:
    RealWebSocket(auto_ptr<Socket> socket, ...);
};

// This is the factory: the function responsible for creating and injecting all dependencies:
WebSocket::pointer WebSocket::from_url(const std::string& url, const std::string& origin)
{
    if ("wss:" == url.substr(0, 4)) {
        auto_ptr<OpenSslSocket> socket(new OpenSslSocket(...));
        return new RealWebSocket(socket, ...);
    }
    else { // assume ws:
        #ifdef _WIN32
        auto_ptr<WinsockSocket> socket(new WinsockSocket(...));
        #else
        auto_ptr<PosixSocket> socket(new PosixSocket(...));
        #endif
        return new RealWebSocket(socket, ...);
    }
}

New to dependency injection? Miško Hevery explains it best: The Clean Code Talks - Don't Look For Things!. It is also described on the Wikipedia SOLID page.

NozzerFX commented 6 years ago

Hi @dhbaird, many thanks for this. I've been trying to use a different C++ lib for this using ASIO but its been a ball ache to implement. It has optional boost support I I dont want to use boost in my VS2017 project. I've got over the issues because boost support can be removed and standalone ASIO can be used. The issue I've had is linking to OpenSSL. I just cannot get it working in ASIO. I've tried multiple different versions et-al. This library looks awesome, but I need to inject OpenSSL. The sample is cool, but did you get this working? How did you get OpenSSL linked in? I would really appreciate some advice because I've never hooked in OpenSSL before. What version did you get working, what libs and headers, and what headers do I need for the above code snippet? Any advice would be extremely appreciated.