7
\$\begingroup\$

Getting close to a release of generic server.

So a couple of reviews will be coming this way over the next few weeks. Stating with the lowest levels and working up to the main server. The result is nodejs like (but not quite as good yet).

My main function for the server now looks like this:

int main()
{
    try
    {
        using ThorsAnvil::Nisse::NisseService;
        using ThorsAnvil::Nisse::ProtocolHTTP::Binder;
        using ThorsAnvil::Nisse::ProtocolHTTP::Request;
        using ThorsAnvil::Nisse::ProtocolHTTP::Response;

        NisseService    service;

        Binder          binder;
        binder.add("/listBeer",[](Request& request, Response& response)
                    {
                        response.headers["Type"] = "BeerList";
                        // The interesting part here. Is that the << operator
                        // will return to the main loop if it would block.
                        // When the socket is ready to accept more data then
                        // The lambda is restarted at the point it left off.
                        // So we get a non blocking single threaded server.
                        response.body << "<html><head></head><body><h1>We have beer</h1></body><ul>";


                        // Now the interesting bit to combine it with the SQL code.
                        using ThorsSQL = ThorsAnvil::SQL;

                        ThorsSQL::Connection    mysql("mysql:://host", "username", "password");
                        ThorsSQL::Statement     beerList(mysql, "SELECT Name, Price FROM Beer WHERE Quality > ?");

                        beerList.execute(ThorsSQL::Bind(request.uri.query("quality")),
                                [&response](u64 id, std::string const& name, int price){
                                    response.body << "<li>" << name << " : " << price << "</li>";
                                }
                        );
                        // We yield this lambda to allow
                        // Data to arrive from the mysql connection
                        // Which is also done asynchronously.
                        // Control will return when the SQL is finished.
                        beerList.yield();


                        response.body << "</ul></html>";
                    }
                   );

        using ThorsAnvil::Nisse::ProtocolHTTP::ReadRequestHandler;
        service.listenOn<ReadRequestHandler>(40716, binder);

        service.start();
    }
    catch (std::exception const& e)
    {
        //Log exception
        throw;
    }
    catch (...)
    {
        //Log exception
        throw;
    }
}

Here is part 1 some (boring) utility functions that are used to build error messages.

#ifndef THORSANVIL_SOCKET_UTILITY_H
#define THORSANVIL_SOCKET_UTILITY_H

#include <string>
#include <sstream>
#include <utility>
#include <cstddef>
#include <cerrno>

namespace ThorsAnvil
{
    namespace Socket
    {

template<typename... Args>
int print(std::ostream& s, Args&... args)
{
    using Expander = int[];
    return Expander{ 0, ((s << std::forward<Args>(args)), 0)...}[0];
}

template<typename... Args>
std::string buildStringFromParts(Args const&... args)
{
    std::stringstream msg;
    print(msg, args...);
    return msg.str();
}

template<typename... Args>
std::string buildErrorMessage(Args const&... args)
{
    return buildStringFromParts(args...);
}

inline std::string errnoToName()
{
    switch (errno)
    {
        case EACCES:            return "EACCES ";
        case EADDRINUSE:        return "EADDRINUSE ";
        case EADDRNOTAVAIL:     return "EADDRNOTAVAIL ";
        case EAFNOSUPPORT:      return "EAFNOSUPPORT ";
        case EAGAIN:            return "EAGAIN ";
        // TODO
        //   Add to configure a test for existence of EWOULDBLOCK
        //case EWOULDBLOCK:       return "EWOULDBLOCK ";
        case EBADF:             return "EBADF ";
        case ECONNABORTED:      return "ECONNABORTED ";
        case ECONNRESET:        return "ECONNRESET ";
        case EDESTADDRREQ:      return "EDESTADDRREQ ";
        case EDQUOT:            return "EDQUOT ";
        case EEXIST:            return "EEXIST ";
        case EFAULT:            return "EFAULT ";
        case EFBIG:             return "EFBIG ";
        case EINTR:             return "EINTR ";
        case EINVAL:            return "EINVAL ";
        case EIO:               return "EIO ";
        case EISDIR:            return "EISDIR ";
        case ELOOP:             return "ELOOP ";
        case EMFILE:            return "EMFILE ";
        case ENAMETOOLONG:      return "ENAMETOOLONG ";
        case ENETDOWN:          return "ENETDOWN ";
        case ENETUNREACH:       return "ENETUNREACH ";
        case ENFILE:            return "ENFILE ";
        case ENOBUFS:           return "ENOBUFS ";
        case ENOENT:            return "ENOENT ";
        case ENOMEM:            return "ENOMEM ";
        case ENOSPC:            return "ENOSPC ";
        case ENOTCONN:          return "ENOTCONN ";
        case ENOTDIR:           return "ENOTDIR ";
        case ENOTSOCK:          return "ENOTSOCK ";
        case ENXIO:             return "ENXIO ";
        case EOPNOTSUPP:        return "EOPNOTSUPP ";
        case EPIPE:             return "EPIPE ";
        case EPROTONOSUPPORT:   return "EPROTONOSUPPORT ";
        case EPROTOTYPE:        return "EPROTOTYPE ";
        case EROFS:             return "EROFS ";
        case ESPIPE:            return "ESPIPE ";
        case ETIMEDOUT:         return "ETIMEDOUT ";
        case EALREADY:          return "EALREADY ";
        case ECONNREFUSED:      return "ECONNREFUSED ";
        case EHOSTUNREACH:      return "EHOSTUNREACH ";
        case EINPROGRESS:       return "EINPROGRESS ";
        case EISCONN:           return "EISCONN ";
        default:
            break;
    }
    return "Unknown: ";
}

inline std::string systemErrorMessage()
{
    std::string result = errnoToName();
    result += strerror(errno);
    return result;
}

    }
}

#endif
\$\endgroup\$
3
\$\begingroup\$

Always a pleasure to review your code.

  1. I'm not sure why you would want to use perfect-forwarding for print(), as any stream-inserter neither accepting its second argument by constant reference nor, though only if easily copied, by value is arguably broken.
    Anyway, you only do half the task of perfect forwarding, as the function-parameter uses & instead of &&.

  2. If you cater to so exotic types that they aren't neccessarily inserted by constant-reference, you might also want to allow the return-value of operator<< to be something surprising which might have overloaded operator,: Separate the stream-insertion and the value by an additional void() to make overloading the comma-operator impossible.

  3. I wonder why you return 0 from print(). You never use it, and it might lead to marginally smaller and more efficient code not to return anything.

  4. errnoToName() is a blatant violation of DRY. Use a simple macro to reduce the repetition:

    #define X(x) case x : return # x " ";
    X(EACCESS)
    X(EADDRINUSE)
    ...
    #undef X
    
  5. I wonder why you have an explicit default: in which you just break out of the switch and then return a default-value.
    Either move the return up into the default: or remove the default: altogether. It's cleaner.

  6. If you didn't limit yourself to C++14, I would suggest using std::string_view for the return-type. At least SSO will likely mean there's no allocation anyway.

  7. Nothing to say about your main() aside from the comment about logging should be replaced by actual logging... you probably just removed it for us.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.