├── preview.gif ├── main.cpp ├── SimpleHTTP.pro ├── .gitignore ├── README.md ├── LICENSE ├── serverlistener.h ├── requestparser.h ├── requestparser.cpp ├── serverexceptions.h └── serverlistener.cpp /preview.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hckr/SimpleHTTP/HEAD/preview.gif -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "serverlistener.h" 3 | 4 | int main() { 5 | ServerListener().run(); 6 | return 0; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /SimpleHTTP.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | CONFIG += console 3 | CONFIG -= app_bundle 4 | CONFIG -= qt 5 | 6 | LIBS += -lws2_32 7 | 8 | CONFIG += c++11 9 | 10 | SOURCES += main.cpp \ 11 | serverlistener.cpp \ 12 | requestparser.cpp 13 | 14 | HEADERS += \ 15 | serverlistener.h \ 16 | requestparser.h \ 17 | serverexceptions.h 18 | 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *-build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | #QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | 36 | *.exe 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Very Simple HTTP Server using Winsock 2 | 3 | * Listens on port `80` 4 | * Responds with received HTTP headers 5 | * Sometimes crashes 😕 6 | 7 | ![Preview in browser](preview.gif) 8 | 9 | 10 | # How to build 11 | 12 | ## On Windows 13 | Use provided Qt Creator project file. 14 | 15 | ## On Linux 16 | It'd probably be easier to rewrite it to BSD sockets, but in current version it can be cross-compiled for Windows and then run with Wine: 17 | 18 | ``` 19 | $ i686-w64-mingw32-g++ *.cpp *.h -o server -static -static-libgcc -static-libstdc++ -lws2_32 20 | $ sudo wine server.exe 21 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Jakub Młokosiewicz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /serverlistener.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVERLISTENER_H 2 | #define SERVERLISTENER_H 3 | 4 | /** 5 | * @file serverlistener.h 6 | * @brief File contains definition of ServerListener 7 | */ 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "serverexceptions.h" 17 | 18 | /** 19 | * @brief Creates an HTTP server instance and awaits requests 20 | * 21 | * Class uses WinSock library. 22 | * 23 | * @warning Server handles only HTTP GET requests (and shows some debug data in response) 24 | * @warning Threads responsible for handling clients aren't really being taken care of 25 | * @todo Handle more HTTP methods 26 | * @todo Give the opportunity to handle client requests to the user (some kind of routing) 27 | */ 28 | class ServerListener { 29 | int port; 30 | size_t buffer_size; 31 | SOCKET listen_socket = INVALID_SOCKET; 32 | bool server_running; 33 | 34 | static void clientHandler(SOCKET client_socket, size_t buffer_size); 35 | 36 | public: 37 | 38 | /** 39 | * Initialize a new server instance. 40 | * 41 | * @param port Port on which the server will be listening for connections 42 | * @param buffer_size Size of the buffer used to retrieve data from sockets 43 | */ 44 | ServerListener(int port=80, size_t buffer_size=255); 45 | 46 | /** 47 | * Start listening for connections. 48 | * 49 | * @param client_acceptation_error_callback The function receiving the ClientAcceptationException object when a problem with acceptation of new connection occurs 50 | */ 51 | void run(std::function client_acceptation_error_callback = [](ClientAcceptationException) {}); 52 | 53 | /** 54 | * Stop listening for connections (close listening socket). 55 | * 56 | * @warning It could be run only from the different thread, as start() is blocking 57 | */ 58 | void stop(); 59 | 60 | virtual ~ServerListener(); 61 | }; 62 | 63 | #endif // SERVERLISTENER_H 64 | -------------------------------------------------------------------------------- /requestparser.h: -------------------------------------------------------------------------------- 1 | #ifndef REQUESTPARSER_H 2 | #define REQUESTPARSER_H 3 | 4 | /** 5 | * @file requestparser.h 6 | * @brief File contains definition of RequestParser 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | /** 13 | * @brief Extracts data from HTTP headers, given the data received from client socket 14 | * @todo Implement extraction of request body (e.g. in POST requests) 15 | */ 16 | class RequestParser 17 | { 18 | bool half_end_of_line; 19 | bool end_of_line; 20 | bool first_line; 21 | bool beginning; 22 | 23 | std::string method; 24 | std::string path; 25 | std::string proto_ver; 26 | 27 | std::string tmp_header_name; 28 | std::string tmp_header_value; 29 | 30 | char previous_char; 31 | 32 | std::map headers; 33 | bool headers_available; 34 | 35 | public: 36 | 37 | /** 38 | * Initialize a new parser instance. 39 | */ 40 | RequestParser(); 41 | 42 | /** 43 | * Feeds the parser with next portion of data from client. 44 | * Should be called in loop, after each chunk of data is received. 45 | * 46 | * @param buf Pointer to the buffer in which data from client socket is stored 47 | * @param size Size of the chunk of data 48 | */ 49 | void processChunk(const char *buf, size_t size); 50 | 51 | /** 52 | * Check if the end of headers was reached by the parser. 53 | * 54 | * @return Information if all data was already extracted from headers and can be safely accessed 55 | */ 56 | bool allHeadersAvailable(); 57 | 58 | /** 59 | * Get extracted headers. 60 | * 61 | * @return Headers in form of std::map (name -> value) 62 | */ 63 | std::map getHeaders(); 64 | 65 | /** 66 | * @return String representing the HTTP method used in request 67 | */ 68 | std::string getMethod(); 69 | 70 | /** 71 | * @return String representing the requested path 72 | */ 73 | std::string getPath(); 74 | 75 | /** 76 | * @return String representing the protocol (and version) used to perform the request 77 | */ 78 | std::string getProtocol(); 79 | 80 | /** 81 | * Prepare the object to handle another request (clear all extracted data). 82 | */ 83 | void reset(); 84 | 85 | virtual ~RequestParser() = default; 86 | }; 87 | 88 | #endif // REQUESTPARSER_H 89 | -------------------------------------------------------------------------------- /requestparser.cpp: -------------------------------------------------------------------------------- 1 | #include "requestparser.h" 2 | 3 | RequestParser::RequestParser() { 4 | reset(); 5 | } 6 | 7 | void RequestParser::reset() { 8 | half_end_of_line = false; 9 | end_of_line = false; 10 | first_line = true; 11 | beginning = true; 12 | 13 | method = ""; 14 | path = ""; 15 | proto_ver = ""; 16 | 17 | tmp_header_name = ""; 18 | tmp_header_value = ""; 19 | 20 | previous_char = '\0'; 21 | 22 | headers.clear(); 23 | headers_available = false; 24 | } 25 | 26 | void RequestParser::processChunk(const char *buf, size_t size) { 27 | char c; 28 | size_t i = 0; 29 | c = buf[i]; 30 | for(; i < size; ++i, c = buf[i]) { 31 | if(c == '\r') { 32 | half_end_of_line = true; 33 | goto next_iter; 34 | } else if(half_end_of_line && c == '\n') { 35 | if(end_of_line) { 36 | headers_available = true; 37 | } else { 38 | if(!first_line) { 39 | headers[tmp_header_name] = tmp_header_value; 40 | tmp_header_name = ""; 41 | tmp_header_value = ""; 42 | } 43 | end_of_line = true; 44 | first_line = false; 45 | goto next_iter; 46 | } 47 | } 48 | if(first_line) { 49 | static int field = 0; 50 | if(beginning || end_of_line) { 51 | field = 0; 52 | } 53 | 54 | if(c == ' ') { 55 | field++; 56 | } else { 57 | switch(field) { 58 | case 0: 59 | method += c; 60 | break; 61 | 62 | case 1: 63 | path += c; 64 | break; 65 | 66 | case 2: 67 | proto_ver += c; 68 | break; 69 | } 70 | } 71 | } else { 72 | static int field = 0; 73 | if(end_of_line) { 74 | field = 0; 75 | } 76 | 77 | switch(field) { 78 | case 0: 79 | if(c == ' ' && previous_char == ':') { 80 | tmp_header_name.pop_back(); 81 | field++; 82 | } else { 83 | tmp_header_name += c; 84 | } 85 | break; 86 | 87 | case 1: 88 | tmp_header_value += c; 89 | break; 90 | } 91 | } 92 | half_end_of_line = false; 93 | end_of_line = false; 94 | next_iter: 95 | previous_char = c; 96 | beginning = false; 97 | } 98 | } 99 | 100 | bool RequestParser::allHeadersAvailable() { 101 | return headers_available; 102 | } 103 | 104 | std::map RequestParser::getHeaders() { 105 | return headers; 106 | } 107 | 108 | std::string RequestParser::getMethod() { 109 | return method; 110 | } 111 | 112 | std::string RequestParser::getPath() { 113 | return path; 114 | } 115 | 116 | std::string RequestParser::getProtocol() { 117 | return proto_ver; 118 | } 119 | -------------------------------------------------------------------------------- /serverexceptions.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #ifndef SERVEREXCEPTIONS_H 5 | #define SERVEREXCEPTIONS_H 6 | 7 | /** 8 | * @file serverexceptions.h 9 | * @brief File contains definitions of exceptions being thrown by ServerListener 10 | */ 11 | 12 | /** 13 | * @brief Parent class for all exceptions being thrown by the ServerListener 14 | */ 15 | class ServerException : public std::runtime_error { 16 | public: 17 | /** 18 | * @param info Information about the exception (possibly shown to the user) 19 | */ 20 | ServerException(std::string info) : std::runtime_error(info) {} 21 | 22 | virtual ~ServerException() {} 23 | }; 24 | 25 | /** 26 | * @brief Exception being thrown in case of socket library initialization error 27 | */ 28 | class ServerStartupException : public ServerException { 29 | public: 30 | ServerStartupException() 31 | : ServerException("Socket library initialization failed") {} 32 | virtual ~ServerStartupException() {} 33 | }; 34 | 35 | 36 | /** 37 | * @brief Exception being thrown in case of addrinfo() returning an error code 38 | */ 39 | class AddrinfoException : public ServerException { 40 | public: 41 | AddrinfoException(int error_no) 42 | : ServerException( 43 | std::string("addrinfo() failed with error: ") + 44 | std::to_string(error_no) 45 | ) {} 46 | virtual ~AddrinfoException() {} 47 | }; 48 | 49 | 50 | /** 51 | * @brief Exception being thrown in case of socket() returning an error code 52 | */ 53 | class SocketCreationException : public ServerException { 54 | public: 55 | SocketCreationException(int error_no) 56 | : ServerException( 57 | std::string("socket() failed with error: ") + 58 | std::to_string(error_no) 59 | ) {} 60 | virtual ~SocketCreationException() {} 61 | }; 62 | 63 | 64 | /** 65 | * @brief Exception being thrown in case of bind() returning an error code 66 | */ 67 | class SocketBindingException : public ServerException { 68 | public: 69 | SocketBindingException(int error_no) 70 | : ServerException( 71 | std::string("bind() failed with error: ") + 72 | std::to_string(error_no) 73 | ) {} 74 | virtual ~SocketBindingException() {} 75 | }; 76 | 77 | 78 | /** 79 | * @brief Exception being thrown in case of listen() returning an error code 80 | */ 81 | class ListenException : public ServerException { 82 | public: 83 | ListenException(int error_no) 84 | : ServerException( 85 | std::string("listen() failed with error: ") + 86 | std::to_string(error_no) 87 | ) {} 88 | virtual ~ListenException() {} 89 | }; 90 | 91 | 92 | /** 93 | * @brief Exception being thrown in case of accept() returning an error code 94 | */ 95 | class ClientAcceptationException : public ServerException { 96 | public: 97 | ClientAcceptationException(int error_no) 98 | : ServerException( 99 | std::string("accept() failed with error: ") + 100 | std::to_string(error_no) 101 | ) {} 102 | virtual ~ClientAcceptationException() {} 103 | }; 104 | 105 | #endif // SERVEREXCEPTIONS_H 106 | 107 | -------------------------------------------------------------------------------- /serverlistener.cpp: -------------------------------------------------------------------------------- 1 | #include "serverlistener.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "requestparser.h" 12 | 13 | ServerListener::ServerListener(int port, size_t buffer_size) { 14 | this->port = port; 15 | this->buffer_size = buffer_size; 16 | this->server_running = false; 17 | this->listen_socket = INVALID_SOCKET; 18 | 19 | WSADATA wsaData; 20 | if(WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) { 21 | throw ServerStartupException(); 22 | } 23 | } 24 | 25 | void ServerListener::run(std::function client_acceptation_error_callback) { 26 | std::shared_ptr socket_props(nullptr, [](addrinfo* ai) { freeaddrinfo(ai); }); 27 | addrinfo hints; 28 | ZeroMemory(&hints, sizeof(hints)); 29 | 30 | hints.ai_family = AF_INET; 31 | hints.ai_socktype = SOCK_STREAM; 32 | hints.ai_protocol = IPPROTO_TCP; 33 | hints.ai_flags = AI_PASSIVE; 34 | 35 | int addrinfo_status = getaddrinfo(NULL, std::to_string(port).c_str(), &hints, (addrinfo**)&socket_props); 36 | if(addrinfo_status != 0) { 37 | throw AddrinfoException(addrinfo_status); 38 | } 39 | 40 | listen_socket = socket(socket_props->ai_family, socket_props->ai_socktype, socket_props->ai_protocol); 41 | if(listen_socket == INVALID_SOCKET) { 42 | throw SocketCreationException(WSAGetLastError()); 43 | } 44 | 45 | if(bind(listen_socket, socket_props->ai_addr, (int)socket_props->ai_addrlen) == SOCKET_ERROR) { 46 | closesocket(listen_socket); 47 | throw SocketBindingException(WSAGetLastError()); 48 | } 49 | 50 | if(listen(listen_socket, SOMAXCONN) == SOCKET_ERROR) { 51 | closesocket(listen_socket); 52 | throw ListenException(WSAGetLastError()); 53 | } 54 | 55 | std::map threads; 56 | 57 | bool server_running = true; 58 | while(server_running) { 59 | SOCKET client_socket; 60 | 61 | try { 62 | client_socket = accept(listen_socket, NULL, NULL); 63 | if(client_socket == INVALID_SOCKET) { 64 | throw ClientAcceptationException(WSAGetLastError()); 65 | } 66 | } catch(ClientAcceptationException &e) { 67 | client_acceptation_error_callback(e); 68 | continue; 69 | } 70 | threads[client_socket] = std::thread(ServerListener::clientHandler, client_socket, buffer_size); 71 | } 72 | } 73 | 74 | void ServerListener::stop() { 75 | server_running = false; 76 | if(listen_socket != INVALID_SOCKET) { 77 | shutdown(listen_socket, SD_BOTH); 78 | closesocket(listen_socket); 79 | } 80 | } 81 | 82 | void ServerListener::clientHandler(SOCKET client_socket, size_t buffer_size) { 83 | int recvbuflen = buffer_size; 84 | char recvbuf[recvbuflen]; 85 | int bytes_received; 86 | RequestParser parser; 87 | 88 | sockaddr_in client_info; 89 | int client_info_len = sizeof(sockaddr_in); 90 | char *client_ip; 91 | 92 | if(getpeername(client_socket, (sockaddr*)(&client_info), &client_info_len) == SOCKET_ERROR) { 93 | goto cleanup; 94 | } 95 | client_ip = inet_ntoa(client_info.sin_addr); 96 | 97 | while(1) { 98 | parser.reset(); 99 | 100 | bool headers_ready = false; 101 | while(!headers_ready) { 102 | bytes_received = recv(client_socket, recvbuf, recvbuflen, 0); 103 | if(bytes_received > 0) { 104 | parser.processChunk(recvbuf, bytes_received); 105 | if(parser.allHeadersAvailable()) { 106 | headers_ready = true; 107 | } 108 | } else { 109 | goto cleanup; 110 | } 111 | } 112 | 113 | auto headers = parser.getHeaders(); 114 | 115 | auto conn_it = headers.find("Connection"); 116 | if(conn_it != headers.end() && conn_it->second == "close") { 117 | goto cleanup; 118 | } 119 | 120 | std::cout << parser.getMethod() << " " 121 | << parser.getPath() << " " 122 | << parser.getProtocol() << "\n"; 123 | 124 | std::cout << "> " << client_ip << "\n"; 125 | 126 | auto ua_it = headers.find("User-Agent"); 127 | if(ua_it != headers.end()) { 128 | std::cout << "> " << ua_it->second << "\n"; 129 | } else { 130 | std::cout << "> no UAString provided" << "\n"; 131 | } 132 | 133 | std::cout << "\n"; 134 | 135 | std::string response_body = "" 136 | "Request info" 137 | "" 143 | "" 144 | "

Request info

"; 145 | 146 | response_body += ""; 147 | 148 | response_body += ""; 149 | response_body += ""; 150 | response_body += ""; 151 | 152 | for(const auto &header : headers) { 153 | response_body += ""; 154 | } 155 | 156 | std::stringstream ss; 157 | ss << std::this_thread::get_id(); 158 | response_body += ""; 159 | 160 | response_body += "
Method" + parser.getMethod() + "
Path" + parser.getPath() + "
Protocol" + parser.getProtocol() + "
" + header.first + "" + header.second + "
Thread ID" + ss.str() + "
"; 161 | response_body += "\r\n"; 162 | 163 | 164 | std::string response_headers = "HTTP/1.1 200 OK\r\n" 165 | "Content-Type: text/html; charset=UTF-8\r\n" 166 | "Connection: keep-alive\r\n" 167 | "Content-Length: " + std::to_string(response_body.length()) + "\r\n\r\n"; 168 | 169 | std::string response = response_headers + response_body; 170 | send(client_socket, response.c_str(), strlen(response.c_str()), 0); 171 | } 172 | cleanup: 173 | closesocket(client_socket); 174 | } 175 | 176 | ServerListener::~ServerListener() { 177 | WSACleanup(); 178 | } 179 | --------------------------------------------------------------------------------