davidrmiller / neural2d

Neural net optimized for 2D image data
MIT License
142 stars 69 forks source link

Webserver on Windows #32

Open ghost opened 8 years ago

ghost commented 8 years ago

I'm working on making the Webserver work on Windows. I like working inside the Visual C++ 2015 environment. Here's some initial code if you can use it. Still needs more testing.

/ webserver.cpp -- embedded web server for the GUI for the neural2d program. David R. Miller, 2014, 2015 For more information, see neural2d.h and https://github.com/davidrmiller/neural2d . /

// C Standard includes

include

include

include

// C++ Standard includes

include

include

include

include

include

include

// POSIX Includes

ifndef WIN32

include // POSIX, for read(), write(), close()

include <sys/types.h> // For setsockopt() and SO_REUSEADDR

endif

include "webserver.h"

int readSocket(int fd,void _buf,sizet count) { return recv(fd, (char) buf, count, 0); }

int writeSocket(int fd,const char *buf,size_t count) { return send(fd, buf,count,0); }

void closeSocket(SOCKET socketFd) {

ifdef WIN32

closesocket(socketFd);

else

close(socketFd);

endif

}

void closeIt(SOCKET socketFd) {

ifdef WIN32

shutdown(socketFd,SD_BOTH);
closesocket(socketFd);

else

shutdown(socketFd,SHUT_RDWR);
close(socketFd);

endif

}

namespace NNet {

// *** Web server ***

WebServer::WebServer(void) {

ifdef WIN32

WORD wVersionRequested;
WSADATA wsaData;
int err;

wVersionRequested = MAKEWORD(2,2);

err = WSAStartup(wVersionRequested,&wsaData);
if(err != 0)
{
    /* Tell the user that we could not find a usable */
    /* WinSock DLL.                                  */
    //      TRACE("No socket dll");
}

endif

socketFd = -1;
firstAccess = true;        // Used to detect when the first HTTP GET arrives
initializeHttpResponse();

}

WebServer::~WebServer(void) { stopServer(); }

void WebServer::stopServer(void) { if (socketFd != -1) { //std::cout << "Closing webserver socket..." << std::endl; closeIt(socketFd); socketFd = -1; } }

// The web server runs in a separate thread. It receives a GET from the browser, // extracts the message (e.g., "eta=0.1") and appends it to a shared message // queue. Periodically, the Net object will pop messages and respond to them // by constructing a parameter block (a string) and calling a function to // send the http response. // void WebServer::initializeHttpResponse(void) { // Keep appending lines to firstPart until we get the line with the sentinel: std::ifstream httpTemplate("http-response-template.txt"); if (!httpTemplate.is_open()) { firstPart = "Cannot open file \"http-response-template.txt\"; web server is disabled.\r\n"; return; }

firstPart = "";
secondPart = "";

int part = 1; // The lines before the sentinel goes into firstPart

while (httpTemplate.good()) {
    std::string line;
    std::getline(httpTemplate, line);

    // Add \r\n; the \r may already be there:

    if (line[line.length() - 1] == '\r') {
        line.append("\n");
    } else {
        line.append("\r\n");
    }

    // Save the line:

    if (part == 1) {
        firstPart.append(line);
    } else {
        secondPart.append(line);
    }

    // If this is the line with the sentinel, we'll change to secondPart for the rest:

    if (line.find("Parameter block") != std::string::npos) {
        part = 2;
    }
}

}

// This sends a 404 Not Found reply // void WebServer::replyToUnknownRequest(int httpConnectionFd) { std::string response = "HTTP/1.1 404 Not Found\r\nContent-Type: text/html\r\nConnection: close\r\n\r\n";

send(httpConnectionFd, response.c_str(), response.length(), 0);
closeIt(httpConnectionFd);

/ shutdown(httpConnectionFd, SHUT_RDWR); close(httpConnectionFd); / }

// Look for "POST /" or "GET /?" followed by a message or POST. // If this is the first GET or POST we receive, we'll treat it as if it were // "GET / " of the root document. // void WebServer::extractAndQueueMessage(std::string s, int httpConnectionFd, MessageQueue &messages) { struct Message_t msg; size_t pos;

if (firstAccess) {
    msg.text = ""; // Causes a default web form html page to be sent back
    firstAccess = false;
} else if ((pos = s.find("POST /")) != std::string::npos) {
    // POST
    pos = s.find("\r\n\r\n");
    assert(pos != std::string::npos);
    msg.text = s.substr(pos + 4);
} else if ((pos = s.find("GET /?")) != std::string::npos) {
    // GET with an argument
    std::string rawMsg = s.substr(pos + 6);
    pos = rawMsg.find(" ");
    assert(pos != std::string::npos);
    msg.text = rawMsg.substr(0, pos);
} else if ((pos = s.find("GET /")) != std::string::npos) {
    // GET without an argument
    msg.text = "";
} else {
    // Unknown HTTP request
    replyToUnknownRequest(httpConnectionFd);
    return;
}

//std::cout << "msg.text = " << msg.text << std::endl;

msg.httpResponseFileDes = httpConnectionFd;
messages.push(msg);

}

void WebServer::start(int portNumber, MessageQueue &messages) { portNumber = portNumber; std::thread webThread(&WebServer::webServerThread, this, portNumber, std::ref(messages)); webThread.detach(); }

// The argument parameterBlock needs to be passed by value because it comes from // a different thread: // void WebServer::sendHttpResponse(std::string parameterBlock, int httpResponseFileDes) { std::string response = firstPart + parameterBlock + secondPart; const char *buf = response.c_str();

size_t lenToSend = response.length();
while (lenToSend > 0) {
    int numWritten = writeSocket(httpResponseFileDes, buf, lenToSend);
    if (numWritten < 0) {
        break; // Serious error
    }
    lenToSend -= numWritten;
    buf += numWritten;
}

closeIt(httpResponseFileDes);

/ shutdown(httpResponseFileDes, SHUT_RDWR); close(httpResponseFileDes); / }

void WebServer::webServerThread(int portNumber, MessageQueue &messages) { static const struct sockaddr_in zero_sockaddr_in = { 0 }; struct sockaddr_in stSockAddr = zero_sockaddr_in; char buff[2048];

socketFd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);

if (-1 == socketFd) {
    std::cerr << "Cannot create socket for the web server interface" << std::endl;
    //exit(1);
    return;
}

stSockAddr.sin_family = AF_INET;
stSockAddr.sin_port = htons(portNumber);
stSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);

// Set SO_REUSEADDR so we can bind the port even if there are
// connections in the TIME_WAIT state, thus allowing the webserver
// to be restarted without waiting for the TIME_WAIT to expire.

int optval = 1;

ifndef WIN32

setsockopt(socketFd,SOL_SOCKET,SO_REUSEADDR,NULL, &optval,sizeof(optval));

else

setsockopt(socketFd,SOL_SOCKET,SO_REUSEADDR, (char*) &optval,sizeof(optval));

endif

if (-1 == bind(socketFd, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr))) {
    std::cerr << "Cannot bind socket for the web server interface\n\n" << std::endl;
    closeSocket(socketFd);
    exit(1);
    return;
}

if (-1 == listen(socketFd, 10)) {
    std::cerr << "Web server network failure" << std::endl;
    closeSocket(socketFd);
    //exit(1);
    return;
}

//std::cout << "Web server started." << std::endl;

while (true) {
    int httpConnectionFd = accept(socketFd, NULL, NULL);

    if (0 > httpConnectionFd) {
        std::cerr << "Webserver failed to accept connection" << std::endl;
        closeSocket(socketFd);
        //exit(1);
        return;
    }

    uint32_t numChars = readSocket(httpConnectionFd, buff, sizeof buff - 1);

    if (numChars > 0) {
        assert(numChars < sizeof buff);
        buff[numChars] = '\0';
        extractAndQueueMessage(buff, httpConnectionFd, messages);
    }
}

closeSocket(socketFd);

}

} /* end namespace NNet */

davidrmiller commented 8 years ago

This is awesome. Thanks for sharing the work you did on this. I look forward to firing up a Windows VM and trying this out on my copy of Visual Studio 2013. If any other Windows experts can test this out and report on the results, it would be greatly appreciated.

Edit: My copy of Visual Studio Community 2013 is complaining about licensing, and after half an hour of research and experimentation, I haven't yet gotten it to work. I feel quite lost in the Microsoft world. I better step back from this and let someone who knows what they are doing test these changes with Visual Studio. All we need is an independent review and confirmation that the changes work and then we can merge the changes into the repository.

davidrmiller commented 7 years ago

Hi @jakeally, I got an email notice saying you had left a comment in this thread, and I tried to respond by email, but it bounced. So, if I've dropped the ball on one of your comments, please open a new issue or add a new comment here. Cheers.