{"action":"created","issue":{"url":"https://api.github.com/repos/davidrmiller/neural2d/issues/32","repository_url":"https://api.github.com/repos/davidrmiller/neural2d","labels_url":"https://api.github.com/repos/davidrmiller/neural2d/issues/32/labels{/name}","comments_url":"https://api.github.com/repos/davidrmiller/neural2d/issues/32/comments","events_url":"https://api.github.com/repos/davidrmiller/neural2d/issues/32/events","html_url":"https://github.com/davidrmiller/neural2d/issues/32","id":174801904,"number":32,"title":"Webserver on Windows","user":{"login":"jakeally","id":19364955,"avatar_url":"https://avatars.githubusercontent.com/u/19364955?v=3","gravatar_id":"","url":"https://api.github.com/users/jakeally","html_url":"https://github.com/jakeally","followers_url":"https://api.github.com/users/jakeally/followers","following_url":"https://api.github.com/users/jakeally/following{/other_user}","gists_url":"https://api.github.com/users/jakeally/gists{/gist_id}","starred_url":"https://api.github.com/users/jakeally/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jakeally/subscriptions","organizations_url":"https://api.github.com/users/jakeally/orgs","repos_url":"https://api.github.com/users/jakeally/repos","events_url":"https://api.github.com/users/jakeally/events{/privacy}","received_events_url":"https://api.github.com/users/jakeally/received_events","type":"User","site_admin":false},"labels":[],"state":"open","locked":false,"assignee":null,"assignees":[],"milestone":null,"comments":0,"created_at":"2016-09-02T16:43:54Z","updated_at":"2016-09-05T16:37:18Z","closed_at":null,"body":"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.\r\n\r\n/*\r\nwebserver.cpp -- embedded web server for the GUI for the neural2d program.\r\nDavid R. Miller, 2014, 2015\r\nFor more information, see neural2d.h and https://github.com/davidrmiller/neural2d .\r\n*/\r\n\r\n// C Standard includes\r\n#include <cassert>\r\n#include <cstdint>\r\n#include <cstdlib>\r\n\r\n// C++ Standard includes\r\n#include <fstream>\r\n#include <iostream>\r\n#include <sstream>\r\n#include <string>\r\n#include <thread>\r\n#include <functional>\r\n\r\n// POSIX Includes\r\n#ifndef WIN32\r\n#include <unistd.h>     // POSIX, for read(), write(), close()\r\n#include <sys/types.h>  // For setsockopt() and SO_REUSEADDR\r\n#endif\r\n\r\n\r\n#include \"webserver.h\"\r\n\r\nint readSocket(int fd,void *buf,size_t count)\r\n{\r\n\treturn recv(fd, (char*) buf, count, 0);\r\n}\r\n\r\nint writeSocket(int fd,const char *buf,size_t count)\r\n{\r\n\treturn send(fd, buf,count,0);\r\n}\r\n\r\n\r\nvoid closeSocket(SOCKET socketFd)\r\n{\r\n#ifdef WIN32\r\n\tclosesocket(socketFd);\r\n#else\r\n\tclose(socketFd);\r\n#endif\r\n}\r\n\r\nvoid closeIt(SOCKET socketFd)\r\n{\r\n#ifdef WIN32\r\n\tshutdown(socketFd,SD_BOTH);\r\n\tclosesocket(socketFd);\r\n#else\r\n\tshutdown(socketFd,SHUT_RDWR);\r\n\tclose(socketFd);\r\n#endif\r\n\r\n\r\n\r\n}\r\n\r\nnamespace NNet {\r\n\r\n//  ***********************************  Web server  ***********************************\r\n\r\nWebServer::WebServer(void)\r\n{\r\n#ifdef WIN32\r\n\tWORD wVersionRequested;\r\n\tWSADATA wsaData;\r\n\tint err;\r\n\r\n\twVersionRequested = MAKEWORD(2,2);\r\n\r\n\terr = WSAStartup(wVersionRequested,&wsaData);\r\n\tif(err != 0)\r\n\t{\r\n\t\t/* Tell the user that we could not find a usable */\r\n\t\t/* WinSock DLL.                                  */\r\n\t\t//\t\tTRACE(\"No socket dll\");\r\n\t}\r\n#endif\r\n\tsocketFd = -1;\r\n    firstAccess = true;        // Used to detect when the first HTTP GET arrives\r\n    initializeHttpResponse();\r\n}\r\n\r\nWebServer::~WebServer(void)\r\n{\r\n    stopServer();\r\n}\r\n\r\nvoid WebServer::stopServer(void)\r\n{\r\n    if (socketFd != -1) {\r\n        //std::cout << \"Closing webserver socket...\" << std::endl;\r\n\t\tcloseIt(socketFd);\r\n\t\tsocketFd = -1;\r\n    }\r\n}\r\n\r\n\r\n// The web server runs in a separate thread. It receives a GET from the browser,\r\n// extracts the message (e.g., \"eta=0.1\") and appends it to a shared message\r\n// queue. Periodically, the Net object will pop messages and respond to them\r\n// by constructing a parameter block (a string) and calling a function to\r\n// send the http response.\r\n//\r\nvoid WebServer::initializeHttpResponse(void)\r\n{\r\n    // Keep appending lines to firstPart until we get the line with the sentinel:\r\n    std::ifstream httpTemplate(\"http-response-template.txt\");\r\n    if (!httpTemplate.is_open()) {\r\n        firstPart = \"Cannot open file \\\"http-response-template.txt\\\"; web server is disabled.\\r\\n\";\r\n        return;\r\n    }\r\n\r\n    firstPart = \"\";\r\n    secondPart = \"\";\r\n\r\n    int part = 1; // The lines before the sentinel goes into firstPart\r\n\r\n    while (httpTemplate.good()) {\r\n        std::string line;\r\n        std::getline(httpTemplate, line);\r\n\r\n        // Add \\r\\n; the \\r may already be there:\r\n\r\n        if (line[line.length() - 1] == '\\r') {\r\n            line.append(\"\\n\");\r\n        } else {\r\n            line.append(\"\\r\\n\");\r\n        }\r\n\r\n        // Save the line:\r\n\r\n        if (part == 1) {\r\n            firstPart.append(line);\r\n        } else {\r\n            secondPart.append(line);\r\n        }\r\n\r\n        // If this is the line with the sentinel, we'll change to secondPart for the rest:\r\n\r\n        if (line.find(\"Parameter block\") != std::string::npos) {\r\n            part = 2;\r\n        }\r\n    }\r\n}\r\n\r\n\r\n// This sends a 404 Not Found reply\r\n//\r\nvoid WebServer::replyToUnknownRequest(int httpConnectionFd)\r\n{\r\n    std::string response = \"HTTP/1.1 404 Not Found\\r\\nContent-Type: text/html\\r\\nConnection: close\\r\\n\\r\\n\";\r\n\r\n    send(httpConnectionFd, response.c_str(), response.length(), 0);\r\n\tcloseIt(httpConnectionFd);\r\n/*\r\n    shutdown(httpConnectionFd, SHUT_RDWR);\r\n    close(httpConnectionFd);\r\n*/\r\n}\r\n\r\n// Look for \"POST /\" or \"GET /?\" followed by a message or POST.\r\n// If this is the first GET or POST we receive, we'll treat it as if it were\r\n// \"GET / \" of the root document.\r\n//\r\nvoid WebServer::extractAndQueueMessage(std::string s, int httpConnectionFd, MessageQueue &messages)\r\n{\r\n    struct Message_t msg;\r\n    size_t pos;\r\n\r\n    if (firstAccess) {\r\n        msg.text = \"\"; // Causes a default web form html page to be sent back\r\n        firstAccess = false;\r\n    } else if ((pos = s.find(\"POST /\")) != std::string::npos) {\r\n        // POST\r\n        pos = s.find(\"\\r\\n\\r\\n\");\r\n        assert(pos != std::string::npos);\r\n        msg.text = s.substr(pos + 4);\r\n    } else if ((pos = s.find(\"GET /?\")) != std::string::npos) {\r\n        // GET with an argument\r\n        std::string rawMsg = s.substr(pos + 6);\r\n        pos = rawMsg.find(\" \");\r\n        assert(pos != std::string::npos);\r\n        msg.text = rawMsg.substr(0, pos);\r\n    } else if ((pos = s.find(\"GET /\")) != std::string::npos) {\r\n        // GET without an argument\r\n        msg.text = \"\";\r\n    } else {\r\n        // Unknown HTTP request\r\n        replyToUnknownRequest(httpConnectionFd);\r\n        return;\r\n    }\r\n\r\n    //std::cout << \"msg.text = \" << msg.text << std::endl;\r\n\r\n    msg.httpResponseFileDes = httpConnectionFd;\r\n    messages.push(msg);\r\n}\r\n\r\n\r\nvoid WebServer::start(int portNumber_, MessageQueue &messages)\r\n{\r\n    portNumber = portNumber_;\r\n    std::thread webThread(&WebServer::webServerThread, this, portNumber, std::ref(messages));\r\n    webThread.detach();\r\n}\r\n\r\n\r\n// The argument parameterBlock needs to be passed by value because it comes from\r\n// a different thread:\r\n//\r\nvoid WebServer::sendHttpResponse(std::string parameterBlock, int httpResponseFileDes)\r\n{\r\n    std::string response = firstPart + parameterBlock + secondPart;\r\n    const char *buf = response.c_str();\r\n\r\n    size_t lenToSend = response.length();\r\n    while (lenToSend > 0) {\r\n        int numWritten = writeSocket(httpResponseFileDes, buf, lenToSend);\r\n        if (numWritten < 0) {\r\n            break; // Serious error\r\n        }\r\n        lenToSend -= numWritten;\r\n        buf += numWritten;\r\n    }\r\n\r\n\tcloseIt(httpResponseFileDes);\r\n/*\r\n    shutdown(httpResponseFileDes, SHUT_RDWR);\r\n    close(httpResponseFileDes);\r\n*/\r\n}\r\n\r\n\r\nvoid WebServer::webServerThread(int portNumber, MessageQueue &messages)\r\n{\r\n    static const struct sockaddr_in zero_sockaddr_in = { 0 };\r\n    struct sockaddr_in stSockAddr = zero_sockaddr_in;\r\n    char buff[2048];\r\n\r\n    socketFd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);\r\n\r\n    if (-1 == socketFd) {\r\n        std::cerr << \"Cannot create socket for the web server interface\" << std::endl;\r\n        //exit(1);\r\n        return;\r\n    }\r\n\r\n    stSockAddr.sin_family = AF_INET;\r\n    stSockAddr.sin_port = htons(portNumber);\r\n    stSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);\r\n\r\n    // Set SO_REUSEADDR so we can bind the port even if there are\r\n    // connections in the TIME_WAIT state, thus allowing the webserver\r\n    // to be restarted without waiting for the TIME_WAIT to expire.\r\n\r\n    int optval = 1;\r\n#ifndef WIN32\r\n\tsetsockopt(socketFd,SOL_SOCKET,SO_REUSEADDR,NULL, &optval,sizeof(optval));\r\n#else\r\n\tsetsockopt(socketFd,SOL_SOCKET,SO_REUSEADDR, (char*) &optval,sizeof(optval));\r\n#endif\r\n\r\n\r\n    if (-1 == bind(socketFd, (struct sockaddr *)&stSockAddr, sizeof(stSockAddr))) {\r\n        std::cerr << \"Cannot bind socket for the web server interface\\n\\n\" << std::endl;\r\n        closeSocket(socketFd);\r\n        exit(1);\r\n        return;\r\n    }\r\n\r\n    if (-1 == listen(socketFd, 10)) {\r\n        std::cerr << \"Web server network failure\" << std::endl;\r\n        closeSocket(socketFd);\r\n        //exit(1);\r\n        return;\r\n    }\r\n\r\n    //std::cout << \"Web server started.\" << std::endl;\r\n\r\n    while (true) {\r\n        int httpConnectionFd = accept(socketFd, NULL, NULL);\r\n\r\n        if (0 > httpConnectionFd) {\r\n            std::cerr << \"Webserver failed to accept connection\" << std::endl;\r\n            closeSocket(socketFd);\r\n            //exit(1);\r\n            return;\r\n        }\r\n\r\n        uint32_t numChars = readSocket(httpConnectionFd, buff, sizeof buff - 1);\r\n\r\n        if (numChars > 0) {\r\n            assert(numChars < sizeof buff);\r\n            buff[numChars] = '\\0';\r\n            extractAndQueueMessage(buff, httpConnectionFd, messages);\r\n        }\r\n    }\r\n\r\n    closeSocket(socketFd);\r\n}\r\n\r\n} /* end namespace NNet */\r\n\r\n\r\n"},"comment":{"url":"https://api.github.com/repos/davidrmiller/neural2d/issues/comments/244783333","html_url":"https://github.com/davidrmiller/neural2d/issues/32#issuecomment-244783333","issue_url":"https://api.github.com/repos/davidrmiller/neural2d/issues/32","id":244783333,"user":{"login":"davidrmiller","id":9440676,"avatar_url":"https://avatars.githubusercontent.com/u/9440676?v=3","gravatar_id":"","url":"https://api.github.com/users/davidrmiller","html_url":"https://github.com/davidrmiller","followers_url":"https://api.github.com/users/davidrmiller/followers","following_url":"https://api.github.com/users/davidrmiller/following{/other_user}","gists_url":"https://api.github.com/users/davidrmiller/gists{/gist_id}","starred_url":"https://api.github.com/users/davidrmiller/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/davidrmiller/subscriptions","organizations_url":"https://api.github.com/users/davidrmiller/orgs","repos_url":"https://api.github.com/users/davidrmiller/repos","events_url":"https://api.github.com/users/davidrmiller/events{/privacy}","received_events_url":"https://api.github.com/users/davidrmiller/received_events","type":"User","site_admin":false},"created_at":"2016-09-05T16:37:18Z","updated_at":"2016-09-05T16:37:18Z","body":"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.\r\n"}}
{"actor":{"display_login":"davidrmiller"}}