diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..707f4da --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +build +projects/build* +projects/qt-creator/*.user diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5900f8e --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +RM = rm -rf +MKDIR = mkdir -p + +#CXX = g++ +CXXFLAGS += -fPIC -std=c++14 -c -Wall -O2 +LDFLAGS = -shared -ldl -lpthread -lgnutls + +DEFS = POSIX +DEFINES = $(patsubst %, -D%, $(DEFS) ) + +SOURCEDIR = ./src +BUILDDIR = ./build +OBJDIR = $(BUILDDIR)/obj + +SOURCES = $(shell find $(SOURCEDIR) -type f -name '*.cpp') +OBJECTS = $(patsubst $(SOURCEDIR)/%.cpp, $(OBJDIR)/%.o, $(SOURCES) ) + +EXECUTABLE = $(BUILDDIR)/httpserverapp.so + +.PHONY: all clean +all: $(BUILDDIR) $(EXECUTABLE) + +$(BUILDDIR): + $(MKDIR) $@ + +$(EXECUTABLE): $(OBJECTS) + $(CXX) $(OBJECTS) -o $@ $(LDFLAGS) + +$(OBJECTS) : $(OBJDIR)/%.o : $(SOURCEDIR)/%.cpp + @$(MKDIR) $(dir $@) + $(CXX) $(DEFINES) $(CXXFLAGS) $< -o $@ + +clean: + $(RM) $(OBJDIR) $(EXECUTABLE) diff --git a/README.md b/README.md index c55f835..bd7312e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,49 @@ -httpserverapp -============= +# httpserverapp -Sample http application on C++ +Sample application for [C++ http server](https://github.com/awwit/httpserver). -For http server on C++ -See: https://github.com/awwit/httpserver +## Dependencies + +Common: + +* [gnutls](https://www.gnutls.org/) + +Linux: `dl`, `pthread`, `gnutls` + +Windows: `ws2_32.lib`, `libgnutls.dll.a` + +## Build + +Linux: + +```sh +git clone https://github.com/awwit/httpserverapp.git +cd httpserverapp +make +``` + +or + +```sh +git clone https://github.com/awwit/httpserverapp.git +cd httpserverapp +mkdir build +cd build +qbs build -f ./../projects/qt-creator/httpserverapp.qbs release +``` + +Windows: + +```sh +git clone https://github.com/awwit/httpserverapp.git +cd httpserver +mkdir build +cd build +devenv ./../projects/msvs/httpserverapp.sln /build +``` + +# License + +The source codes are licensed under the +[MIT](https://opensource.org/licenses/MIT), +the full text of the license is located in the [LICENSE](LICENSE) file. diff --git a/httpserverapp.sln b/httpserverapp.sln deleted file mode 100644 index 467c721..0000000 --- a/httpserverapp.sln +++ /dev/null @@ -1,20 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 -Project("{2857B73E-F847-4B02-9238-064979017E93}") = "httpserverapp", "httpserverapp\httpserverapp.cproj", "{4C15CBD7-D1C0-4869-AEE1-14CB41328514}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4C15CBD7-D1C0-4869-AEE1-14CB41328514}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4C15CBD7-D1C0-4869-AEE1-14CB41328514}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4C15CBD7-D1C0-4869-AEE1-14CB41328514}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4C15CBD7-D1C0-4869-AEE1-14CB41328514}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(MonoDevelopProperties) = preSolution - StartupItem = httpserverapp\httpserverapp.cproj - EndGlobalSection -EndGlobal diff --git a/httpserverapp.userprefs b/httpserverapp.userprefs deleted file mode 100644 index 5caf378..0000000 --- a/httpserverapp.userprefs +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/httpserverapp/FileIncoming.cpp b/httpserverapp/FileIncoming.cpp deleted file mode 100644 index ee590d2..0000000 --- a/httpserverapp/FileIncoming.cpp +++ /dev/null @@ -1,38 +0,0 @@ - -#include "FileIncoming.h" - -#include - -namespace HttpServer -{ - FileIncoming::FileIncoming(const std::string &fileName, const std::string &fileType, const size_t fileSize) - : file_name(fileName), file_type(fileType), file_size(fileSize) - { - - } - - FileIncoming::FileIncoming(const FileIncoming &file) - : file_name(file.file_name), file_type(file.file_type), file_size(file.file_size) - { - - } - - FileIncoming::FileIncoming(FileIncoming &&file) - : file_name(file.file_name), file_type(file.file_type), file_size(file.file_size) - { - file.file_name.clear(); - file.file_type.clear(); - file.file_size = 0; - } - - bool FileIncoming::isExists() const - { - std::ifstream file(file_name, std::ifstream::binary); - - bool is_exists = file.good(); - - file.close(); - - return is_exists; - } -}; \ No newline at end of file diff --git a/httpserverapp/FileIncoming.h b/httpserverapp/FileIncoming.h deleted file mode 100644 index 75df66c..0000000 --- a/httpserverapp/FileIncoming.h +++ /dev/null @@ -1,40 +0,0 @@ -#pragma once - -#include - -namespace HttpServer -{ - class FileIncoming - { - protected: - std::string file_name; - std::string file_type; - size_t file_size; - - private: - FileIncoming() = delete; - - public: - FileIncoming(const std::string &, const std::string &, const size_t); - FileIncoming(const FileIncoming &); - FileIncoming(FileIncoming &&); - ~FileIncoming() = default; - - inline std::string getName() const - { - return file_name; - } - - inline std::string getType() const - { - return file_type; - } - - inline size_t getSize() const - { - return file_size; - } - - bool isExists() const; - }; -}; \ No newline at end of file diff --git a/httpserverapp/Main.cpp b/httpserverapp/Main.cpp deleted file mode 100644 index 819645a..0000000 --- a/httpserverapp/Main.cpp +++ /dev/null @@ -1,97 +0,0 @@ - -#include "Main.h" - -#include "Test.h" - -#include - -DLLEXPORT bool application_init() -{ - return true; -} - -DLLEXPORT int application_call(HttpServer::server_request *request, HttpServer::server_response *response) -{ - std::unordered_multimap params; - std::unordered_map headers; - std::unordered_multimap data; - std::unordered_multimap files; - std::unordered_multimap cookies; - - Utils::rawPairsToStlUnorderedMultimap(params, request->params, request->params_count); - Utils::rawPairsToStlUnorderedMap(headers, request->headers, request->headers_count); - Utils::rawPairsToStlUnorderedMultimap(data, request->data, request->data_count); - Utils::rawFilesInfoToFilesIncoming(files, request->files, request->files_count); - - auto it_cookie = headers.find("Cookie"); - - if (headers.end() != it_cookie) - { - Utils::parseCookies(it_cookie->second, cookies); - } - - HttpServer::ServerRequest proc_request { - HttpServer::Socket(request->socket), - std::string(request->method), - std::string(request->uri_reference), - std::string(request->document_root), - std::move(params), - std::move(headers), - std::move(data), - std::move(files), - std::move(cookies) - }; - - HttpServer::ServerResponse proc_response { - HttpServer::Socket(request->socket), - std::map() - }; - - std::string absolute_path = proc_request.document_root + proc_request.uri_reference; - - int result = EXIT_SUCCESS; - - std::ifstream file(absolute_path, std::ifstream::binary); - - if (file) - { - auto it_connection = proc_request.headers.find("Connection"); - - if (proc_request.headers.cend() != it_connection) - { - proc_response.headers["Connection"] = it_connection->second; - } - - proc_response.headers["X-Sendfile"] = absolute_path; - } - else - { - result = test(proc_request, proc_response); - } - - file.close(); - - if (proc_response.headers.size() ) - { - Utils::raw_pair *headers; - Utils::stlMapToRawPairs(&headers, proc_response.headers); - - response->headers_count = proc_response.headers.size(); - response->headers = headers; - } - - return result; -} - -DLLEXPORT void application_clear(Utils::raw_pair headers[], const size_t headers_count) -{ - if (headers && headers_count) - { - destroyRawPairs(headers, headers_count); - } -} - -DLLEXPORT void application_final() -{ - -} \ No newline at end of file diff --git a/httpserverapp/Main.h b/httpserverapp/Main.h deleted file mode 100644 index ddc8a58..0000000 --- a/httpserverapp/Main.h +++ /dev/null @@ -1,16 +0,0 @@ -#pragma once - -#include "ServerRequest.h" -#include "ServerResponse.h" -#include "FileIncoming.h" -#include "Utils.h" - -#include -#include -#include - -#ifdef WIN32 - #define DLLEXPORT extern "C" __declspec(dllexport) -#else - #define DLLEXPORT extern "C" -#endif \ No newline at end of file diff --git a/httpserverapp/RawData.h b/httpserverapp/RawData.h deleted file mode 100644 index a493382..0000000 --- a/httpserverapp/RawData.h +++ /dev/null @@ -1,20 +0,0 @@ -#pragma once - -#include - -namespace Utils -{ - struct raw_pair - { - char *key; - char *value; - }; - - struct raw_fileinfo - { - char *key; - char *file_name; - char *file_type; - size_t file_size; - }; -}; \ No newline at end of file diff --git a/httpserverapp/ResourceAbstract.cpp b/httpserverapp/ResourceAbstract.cpp deleted file mode 100644 index aa51959..0000000 --- a/httpserverapp/ResourceAbstract.cpp +++ /dev/null @@ -1,35 +0,0 @@ -#include "ResourceAbstract.h" - -#include "ResourceMethodAbstract.h" -#include "ResourceMethodOptions.h" - -namespace HttpServer -{ - ResourceAbstract::ResourceAbstract() - { - addMethod(new ResourceMethodOptions() ); - } - - ResourceAbstract::~ResourceAbstract() - { - for (auto &method : methods) - { - delete method.second; - } - - for (auto &resource : resources) - { - delete resource.second; - } - } - - void ResourceAbstract::addMethod(ResourceMethodAbstract *method) - { - methods.emplace(method->getName(), method); - } - - void ResourceAbstract::addResource(ResourceAbstract *resource) - { - resources.emplace(resource->getName(), resource); - } -}; diff --git a/httpserverapp/ResourceAbstract.h b/httpserverapp/ResourceAbstract.h deleted file mode 100644 index 3df64af..0000000 --- a/httpserverapp/ResourceAbstract.h +++ /dev/null @@ -1,32 +0,0 @@ -#pragma once - -#include - -namespace HttpServer -{ - class ResourceMethodAbstract; - - class ResourceAbstract - { - protected: - std::string resource_name; - - public: - std::unordered_map methods; - - std::unordered_map resources; - - private: - void addMethod(ResourceMethodAbstract *); - void addResource(ResourceAbstract *); - - public: - ResourceAbstract(); - ~ResourceAbstract(); - - inline std::string getName() const - { - return resource_name; - } - }; -}; diff --git a/httpserverapp/ResourceMethodAbstract.h b/httpserverapp/ResourceMethodAbstract.h deleted file mode 100644 index 42d87a0..0000000 --- a/httpserverapp/ResourceMethodAbstract.h +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -#include "ServerRequest.h" -#include "ServerResponse.h" -#include "ResourceAbstract.h" - -#include - -namespace HttpServer -{ - class ResourceMethodAbstract - { - protected: - std::string resource_method_name; - - public: - inline std::string getName() const - { - return resource_method_name; - } - - virtual void execute(ResourceAbstract *, ServerRequest &, ServerResponse &) = 0; - }; -}; diff --git a/httpserverapp/ResourceMethodOptions.cpp b/httpserverapp/ResourceMethodOptions.cpp deleted file mode 100644 index e895240..0000000 --- a/httpserverapp/ResourceMethodOptions.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include "ResourceMethodOptions.h" - -namespace HttpServer -{ - ResourceMethodOptions::ResourceMethodOptions() - { - resource_method_name = "OPTIONS"; - } - - void ResourceMethodOptions::execute(ResourceAbstract *resource, ServerRequest &request, ServerResponse &response) - { - std::string options; - - for (auto &method : resource->methods) - { - if (method.second != this) - { - options += method.first; - options.push_back(','); - } - } - - if (options.length() ) - { - options.pop_back(); - } - } -}; diff --git a/httpserverapp/ResourceMethodOptions.h b/httpserverapp/ResourceMethodOptions.h deleted file mode 100644 index 2a170a4..0000000 --- a/httpserverapp/ResourceMethodOptions.h +++ /dev/null @@ -1,14 +0,0 @@ -#pragma once - -#include "ResourceMethodAbstract.h" - -namespace HttpServer -{ - class ResourceMethodOptions: public ResourceMethodAbstract - { - public: - ResourceMethodOptions(); - - virtual void execute(ResourceAbstract *, ServerRequest &, ServerResponse &) override; - }; -}; diff --git a/httpserverapp/ServerRequest.h b/httpserverapp/ServerRequest.h deleted file mode 100644 index 0982cc8..0000000 --- a/httpserverapp/ServerRequest.h +++ /dev/null @@ -1,52 +0,0 @@ -#pragma once - -#include "RawData.h" -#include "Socket.h" -#include "FileIncoming.h" - -#include - -namespace HttpServer -{ - struct server_request - { - const System::native_socket_type socket; - const char *method; - const char *uri_reference; - const char *document_root; - const size_t params_count; - const Utils::raw_pair *params; - const size_t headers_count; - const Utils::raw_pair *headers; - const size_t data_count; - const Utils::raw_pair *data; - const size_t files_count; - const Utils::raw_fileinfo *files; - }; - - /** - * Структура запроса (входные данные) - * - * @member const Socket socket - сокет клиента - * @member const std::string method - метод применяемый к ресурсу - * @member const std::string uri_reference - ссылка на ресурс - * @member const std::string document_root - корневая директория приложения - * @member const std::unordered_multimap params - параметры ресурса - * @member const std::unordered_map headers - заголовки запроса - * @member const std::unordered_multimap data - входящие данные запроса - * @member const std::unordered_multimap files - входящие файлы запроса - * @member const std::unordered_multimap cookies - входящие куки запроса - */ - struct ServerRequest - { - const Socket socket; - const std::string method; - const std::string uri_reference; - const std::string document_root; - const std::unordered_multimap params; - const std::unordered_map headers; - const std::unordered_multimap data; - const std::unordered_multimap files; - const std::unordered_multimap cookies; - }; -}; diff --git a/httpserverapp/ServerResponse.h b/httpserverapp/ServerResponse.h deleted file mode 100644 index f59400c..0000000 --- a/httpserverapp/ServerResponse.h +++ /dev/null @@ -1,22 +0,0 @@ -#pragma once - -#include "RawData.h" - -#include -#include - -namespace HttpServer -{ - struct server_response - { - System::native_socket_type socket; - size_t headers_count; - Utils::raw_pair *headers; - }; - - struct ServerResponse - { - Socket socket; - std::map headers; - }; -}; diff --git a/httpserverapp/Socket.cpp b/httpserverapp/Socket.cpp deleted file mode 100644 index bd79fbd..0000000 --- a/httpserverapp/Socket.cpp +++ /dev/null @@ -1,382 +0,0 @@ - -#include "Socket.h" - -namespace HttpServer -{ - int Socket::Startup() - { - #ifdef WIN32 - unsigned short version = MAKEWORD(2, 2); - ::WSADATA wsaData = {0}; - return ::WSAStartup(version, &wsaData); - #elif POSIX - return 0; - #else - #error "Undefine platform" - #endif - } - - int Socket::Cleanup() - { - #ifdef WIN32 - return ::WSACleanup(); - #elif POSIX - return 0; - #else - #error "Undefine platform" - #endif - } - - Socket::Socket(): socket_handle(~0) - { - - } - - Socket::Socket(const System::native_socket_type handle) : socket_handle(handle) - { - - } - - Socket::Socket(const Socket &obj) : socket_handle(obj.socket_handle) - { - - } - - Socket::Socket(Socket &&that) : socket_handle(that.socket_handle) - { - that.socket_handle = ~0; - } - - System::native_socket_type Socket::open() - { - close(); - - socket_handle = ::socket(AF_INET, SOCK_STREAM, 0); - - return socket_handle; - } - - int Socket::close() - { - if (is_open() ) - { - #ifdef WIN32 - int result = ::closesocket(socket_handle); - #elif POSIX - int result = ::close(socket_handle); - #else - #error "Undefine platform" - #endif - - if (0 == result) - { - socket_handle = ~0; - } - - return result; - } - - return ~0; - } - - int Socket::bind(const int port) const - { - ::sockaddr_in sock_addr = {0}; - sock_addr.sin_family = AF_INET; - sock_addr.sin_port = htons(port); - sock_addr.sin_addr.s_addr = ::htonl(INADDR_ANY); - - return ::bind(socket_handle, reinterpret_cast(&sock_addr), sizeof(sockaddr_in) ); - } - - int Socket::listen() const - { - return ::listen(socket_handle, SOMAXCONN); - } - - Socket Socket::accept() const - { - #ifdef WIN32 - System::native_socket_type client_socket = ::accept(socket_handle, static_cast(nullptr), static_cast(nullptr) ); - #elif POSIX - System::native_socket_type client_socket = ::accept(socket_handle, static_cast(nullptr), static_cast(nullptr) ); - #else - #error "Undefine platform" - #endif - return Socket(client_socket); - } - - Socket Socket::nonblock_accept() const - { - System::native_socket_type client_socket = ~0; - #ifdef WIN32 - ::fd_set readset; - FD_ZERO(&readset); - FD_SET(socket_handle, &readset); - - if (0 < ::select(socket_handle + 1, &readset, nullptr, nullptr, nullptr) ) - { - if (FD_ISSET(socket_handle, &readset) ) - { - client_socket = ::accept(socket_handle, static_cast(nullptr), static_cast(nullptr) ); - } - } - #elif POSIX - ::fd_set readset; - FD_ZERO(&readset); - FD_SET(socket_handle, &readset); - - if (0 < ::select(socket_handle + 1, &readset, nullptr, nullptr, nullptr) ) - { - if (FD_ISSET(socket_handle, &readset) ) - { - client_socket = ::accept(socket_handle, static_cast(nullptr), static_cast(nullptr) ); - } - } - #else - #error "Undefine platform" - #endif - return Socket(client_socket); - } - - Socket Socket::nonblock_accept(const std::chrono::milliseconds &timeWait) const - { - System::native_socket_type client_socket = ~0; - #ifdef WIN32 - ::fd_set readset; - FD_ZERO(&readset); - FD_SET(socket_handle, &readset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, &readset, nullptr, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &readset) ) - { - client_socket = ::accept(socket_handle, static_cast(nullptr), static_cast(nullptr) ); - } - } - #elif POSIX - ::fd_set readset; - FD_ZERO(&readset); - FD_SET(socket_handle, &readset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, &readset, nullptr, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &readset) ) - { - client_socket = ::accept(socket_handle, static_cast(nullptr), static_cast(nullptr) ); - } - } - #else - #error "Undefine platform" - #endif - return Socket(client_socket); - } - - int Socket::shutdown() const - { - if (is_open() ) - { - #ifdef WIN32 - return ::shutdown(socket_handle, SD_BOTH); - #elif POSIX - return ::shutdown(socket_handle, SHUT_RDWR); - #else - #error "Undefine platform" - #endif - } - - return -1; - } - - bool Socket::nonblock(const bool isNonBlock) const - { - #ifdef WIN32 - unsigned long value = isNonBlock; - return 0 == ::ioctlsocket(socket_handle, FIONBIO, &value); - #elif POSIX - return -1 != ::fcntl(socket_handle, F_SETFL, isNonBlock ? O_NONBLOCK : O_SYNC); - #else - #error "Undefine platform" - #endif - } - -/* bool Socket::is_nonblock() const - { - #ifdef WIN32 - - #elif POSIX - int flags = ::fcntl(socket_handle, F_GETFL, 0); - return (flags != -1) && (flags & O_NONBLOCK); - #else - #error "Undefine platform" - #endif - }*/ - - size_t Socket::recv(std::vector &buf) const - { - #ifdef WIN32 - return ::recv(socket_handle, buf.data(), buf.size(), 0); - #elif POSIX - return ::recv(socket_handle, buf.data(), buf.size(), MSG_NOSIGNAL); - #else - #error "Undefine platform" - #endif - } - - size_t Socket::nonblock_recv(std::vector &buf, const std::chrono::milliseconds &timeWait) const - { - #ifdef WIN32 - ::fd_set readset; - FD_ZERO(&readset); - FD_SET(socket_handle, &readset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, &readset, nullptr, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &readset) ) - { - return ::recv(socket_handle, buf.data(), buf.size(), 0); - } - } - - return std::numeric_limits::max(); - #elif POSIX - ::fd_set readset; - FD_ZERO(&readset); - FD_SET(socket_handle, &readset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, &readset, nullptr, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &readset) ) - { - return ::recv(socket_handle, buf.data(), buf.size(), MSG_NOSIGNAL); - } - } - - return std::numeric_limits::max(); - #else - #error "Undefine platform" - #endif - } - - size_t Socket::send(const std::string &buf) const - { - #ifdef WIN32 - return ::send(socket_handle, buf.data(), buf.length(), 0); - #elif POSIX - return ::send(socket_handle, buf.data(), buf.length(), MSG_WAITALL | MSG_NOSIGNAL); - #else - #error "Undefine platform" - #endif - } - - size_t Socket::send(const std::vector &buf, const size_t length) const - { - #ifdef WIN32 - return ::send(socket_handle, buf.data(), length, 0); - #elif POSIX - return ::send(socket_handle, buf.data(), length, MSG_WAITALL | MSG_NOSIGNAL); - #else - #error "Undefine platform" - #endif - } - - size_t Socket::nonblock_send(const std::string &buf, const std::chrono::milliseconds &timeWait) const - { - #ifdef WIN32 - ::fd_set writeset; - FD_ZERO(&writeset); - FD_SET(socket_handle, &writeset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, nullptr, &writeset, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &writeset) ) - { - return ::send(socket_handle, buf.data(), buf.length(), 0); - } - } - - return std::numeric_limits::max(); - #elif POSIX - ::fd_set writeset; - FD_ZERO(&writeset); - FD_SET(socket_handle, &writeset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, nullptr, &writeset, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &writeset) ) - { - return ::send(socket_handle, buf.data(), buf.length(), MSG_NOSIGNAL); - } - } - - return std::numeric_limits::max(); - #else - #error "Undefine platform" - #endif - } - - size_t Socket::nonblock_send(const std::vector &buf, const size_t length, const std::chrono::milliseconds &timeWait) const - { - #ifdef WIN32 - ::fd_set writeset; - FD_ZERO(&writeset); - FD_SET(socket_handle, &writeset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, nullptr, &writeset, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &writeset) ) - { - return ::send(socket_handle, buf.data(), length, 0); - } - } - - return std::numeric_limits::max(); - #elif POSIX - ::fd_set writeset; - FD_ZERO(&writeset); - FD_SET(socket_handle, &writeset); - - long seconds = timeWait.count() / 1000; - ::timeval timeout {seconds, (timeWait.count() - seconds * 1000) * 1000}; - - if (0 < ::select(socket_handle + 1, nullptr, &writeset, nullptr, &timeout) ) - { - if (FD_ISSET(socket_handle, &writeset) ) - { - return ::send(socket_handle, buf.data(), length, MSG_WAITALL | MSG_NOSIGNAL); - } - } - - return std::numeric_limits::max(); - #else - #error "Undefine platform" - #endif - } - - Socket &Socket::operator=(const Socket s) - { - socket_handle = s.socket_handle; - return *this; - } -}; \ No newline at end of file diff --git a/httpserverapp/Socket.h b/httpserverapp/Socket.h deleted file mode 100644 index 4774055..0000000 --- a/httpserverapp/Socket.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#ifdef WIN32 - #include - #pragma comment(lib, "ws2_32.lib") - #undef max -#elif POSIX - #include - #include - #include - #include - #include -#else - #error "Undefine platform" -#endif - -#include "System.h" - -#include -#include - -#include -#include -#include - -namespace HttpServer -{ - class Socket - { - protected: - System::native_socket_type socket_handle; - - public: - int static Startup(); - int static Cleanup(); - - public: - Socket(); - Socket(const System::native_socket_type); - Socket(const Socket &); - Socket(Socket &&); - - ~Socket() = default; - - System::native_socket_type open(); - int close(); - - inline bool is_open() const - { - #ifdef WIN32 - return INVALID_SOCKET != socket_handle; - #elif POSIX - return ~0 != socket_handle; - #else - #error "Undefine platform" - #endif - } - - int bind(const int) const; - int listen() const; - - Socket accept() const; - Socket nonblock_accept() const; - Socket nonblock_accept(const std::chrono::milliseconds &) const; - - int shutdown() const; - - bool nonblock(const bool = true) const; - // bool is_nonblock() const; - - size_t recv(std::vector &) const; - size_t nonblock_recv(std::vector &, const std::chrono::milliseconds &) const; - - size_t send(const std::string &) const; - size_t send(const std::vector &, const size_t) const; - - size_t nonblock_send(const std::string &, const std::chrono::milliseconds &) const; - size_t nonblock_send(const std::vector &, const size_t, const std::chrono::milliseconds &) const; - - inline System::native_socket_type get_handle() const - { - return socket_handle; - } - - Socket &operator =(const Socket); - }; -}; \ No newline at end of file diff --git a/httpserverapp/System.cpp b/httpserverapp/System.cpp deleted file mode 100644 index c5f6218..0000000 --- a/httpserverapp/System.cpp +++ /dev/null @@ -1,117 +0,0 @@ - -#include "System.h" - -namespace System -{ -#ifdef WIN32 - struct EnumData - { - native_processid_type process_id; - ::HWND hWnd; - }; - - BOOL WINAPI EnumProc(::HWND hWnd, ::LPARAM lParam) - { - EnumData &ed = *reinterpret_cast(lParam); - - native_processid_type process_id = 0; - - ::GetWindowThreadProcessId(hWnd, &process_id); - - if (process_id == ed.process_id && GetConsoleWindow() != hWnd) - { - ed.hWnd = hWnd; - - return false; - } - - return true; - } -#endif - - bool sendSignal(const native_processid_type pid, const int signal) - { - #ifdef WIN32 - EnumData ed = {pid, 0}; - - ::EnumWindows(EnumProc, reinterpret_cast(&ed) ); - - if (0 == ed.hWnd) - { - return false; - } - - return 0 != ::PostMessage(ed.hWnd, signal, 0, 0); - #elif POSIX - return 0 == ::kill(pid, signal); - #else - #error "Undefine platform" - #endif - } - - bool getFileSizeAndTimeGmt(const std::string &filePath, size_t *fileSize, time_t *fileTime) - { - #ifdef WIN32 - ::HANDLE hFile = ::CreateFile(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, 0, nullptr); - - if (INVALID_HANDLE_VALUE == hFile) - { - return false; - } - - if (false == ::GetFileSizeEx(hFile, reinterpret_cast<::PLARGE_INTEGER>(fileSize) ) ) - { - return false; - } - - ::FILETIME ftWrite; - - ::BOOL result = ::GetFileTime(hFile, nullptr, nullptr, &ftWrite); - - ::CloseHandle(hFile); - - if (false == result) - { - return false; - } - - ::SYSTEMTIME stUtc; - - ::FileTimeToSystemTime(&ftWrite, &stUtc); - - struct ::tm tm_time { - stUtc.wSecond, - stUtc.wMinute, - stUtc.wHour, - stUtc.wDay, - stUtc.wMonth - 1, - stUtc.wYear - 1900, - 0, - 0, - 0 - }; - - *fileTime = ::mktime(&tm_time); - - return true; - #elif POSIX - struct ::tm *clock; - struct ::stat attrib; - - if (-1 == ::stat(filePath.c_str(), &attrib) ) - { - return false; - } - - *fileSize = attrib.st_size; - - clock = ::gmtime(&(attrib.st_mtime) ); - - *fileTime = ::mktime(clock); - - return true; - #else - #error "Undefine platform" - #endif - } -}; \ No newline at end of file diff --git a/httpserverapp/System.h b/httpserverapp/System.h deleted file mode 100644 index 29ee42b..0000000 --- a/httpserverapp/System.h +++ /dev/null @@ -1,89 +0,0 @@ -#pragma once - -#ifdef WIN32 - #include -#elif POSIX - #include - #include - #include - #include -#else - #error "Undefine platform" -#endif - -#ifndef SIGUSR1 - #define SIGUSR1 10 -#endif - -#include -#include -#include - -namespace System -{ -#ifdef WIN32 - typedef ::SOCKET native_socket_type; -#elif POSIX - typedef int native_socket_type; -#else - #error "Undefine platform" -#endif - -#ifdef WIN32 - typedef ::DWORD native_processid_type; -#elif POSIX - typedef ::pid_t native_processid_type; -#else - #error "Undefine platform" -#endif - - inline size_t getProcessorsCount() - { - #ifdef WIN32 - ::SYSTEM_INFO si = {0}; - ::GetSystemInfo(&si); - return si.dwNumberOfProcessors; - #elif POSIX - return ::get_nprocs(); - #else - #error "Undefine platform" - #endif - } - - inline native_processid_type getProcessId() - { - #ifdef WIN32 - return ::GetCurrentProcessId(); - #elif POSIX - return ::getpid(); - #else - #error "Undefine platform" - #endif - } - - bool sendSignal(const native_processid_type pid, const int signal); - - inline bool isDoneThread(const std::thread::native_handle_type handle) - { - #ifdef WIN32 - return WAIT_OBJECT_0 == ::WaitForSingleObject(handle, 0); - #elif POSIX - return 0 != ::pthread_kill(handle, 0); - #else - #error "Undefine platform" - #endif - } - - inline std::string getTempDir() - { - #ifdef WIN32 - return std::string("C:/Temp/"); // FIXME: Windows temp dir - #elif POSIX - return std::string("/tmp/"); - #else - #error "Undefine platform" - #endif - } - - bool getFileSizeAndTimeGmt(const std::string &, size_t *, time_t *); -}; \ No newline at end of file diff --git a/httpserverapp/Test.cpp b/httpserverapp/Test.cpp deleted file mode 100644 index 24bf3ab..0000000 --- a/httpserverapp/Test.cpp +++ /dev/null @@ -1,74 +0,0 @@ - -#include "Test.h" - -bool test(HttpServer::ServerRequest &request, HttpServer::ServerResponse &response) -{ - const std::unordered_map &incoming_headers = request.headers; - const std::unordered_multimap &incoming_data = request.data; - - HttpServer::Socket &socket = response.socket; - std::map &headers_outgoing = response.headers; - - std::string s; - - for (auto h = incoming_headers.cbegin(); h != incoming_headers.cend(); ++h) - { - s += h->first + ":" + h->second + "\n"; - } - - s += "\n"; - - for (auto v = incoming_data.cbegin(); v != incoming_data.cend(); ++v) - { - s += v->first + ": " + v->second + "\n"; - } - -/* s = "\ -\ -\ - 1\ - 2\ -\ -\ - 3\ - 4\ -\ -";*/ - - headers_outgoing[""] = "HTTP/1.1 200 OK"; - headers_outgoing["Content-Type"] = "text/plain; charset=utf-8"; -// headers_outgoing["Access-Control-Allow-Origin"] = "*"; -// headers_outgoing["Content-Type"] = "text/html; charset=utf-8"; - headers_outgoing["Content-Length"] = std::to_string(s.length() ); - headers_outgoing["Connection"] = "keep-alive"; - headers_outgoing["Date"] = Utils::getDatetimeStringValue(); - - std::string headers; - - for (auto h = headers_outgoing.cbegin(); h != headers_outgoing.cend(); ++h) - { - if (h->first.length() ) - { - headers += h->first + ": "; - } - - headers += h->second + "\r\n"; - } - - headers += "\r\n"; - - headers_outgoing.clear(); - -// s = headers + s; - - std::chrono::milliseconds timeout(5000); - - socket.nonblock_send(headers, timeout); - - if ( (size_t)~0 == socket.nonblock_send(s, timeout) ) - { - socket.close(); - } - - return EXIT_SUCCESS; -} diff --git a/httpserverapp/Test.h b/httpserverapp/Test.h deleted file mode 100644 index 254aa58..0000000 --- a/httpserverapp/Test.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include "ServerRequest.h" -#include "ServerResponse.h" - -#include "Utils.h" - -#include - -bool test(HttpServer::ServerRequest &, HttpServer::ServerResponse &); diff --git a/httpserverapp/Utils.cpp b/httpserverapp/Utils.cpp deleted file mode 100644 index 498bc6d..0000000 --- a/httpserverapp/Utils.cpp +++ /dev/null @@ -1,308 +0,0 @@ - -#include "Utils.h" - -#include - -namespace Utils -{ - void trim(std::string &str) - { - size_t last = str.find_last_not_of(" \t\n\v\f\r"); - - if (std::string::npos == last) - { - return str.clear(); - } - - str.assign(str.cbegin() + str.find_first_not_of(" \t\n\v\f\r"), str.cbegin() + last + 1); - } - - char *stlStringToPChar(const std::string &str) - { - size_t length = str.length(); - char *s = nullptr; - - if (length) - { - s = new char[length + 1]; - #ifdef WIN32 - ::strcpy_s(s, length + 1, str.c_str() ); - #elif POSIX - ::strcpy(s, str.c_str() ); - s[length] = '\0'; - #else - #error "Undefine platform" - #endif - } - - return s; - } - - void stlMapToRawPairs(Utils::raw_pair *raw[], const std::map &map) - { - if (raw && map.size() ) - { - raw_pair *arr = new raw_pair[map.size()]; - - *raw = arr; - - size_t i = 0; - - for (auto it = map.cbegin(); map.cend() != it; ++it, ++i) - { - arr[i].key = stlStringToPChar(it->first); - arr[i].value = stlStringToPChar(it->second); - } - } - } - - void stlUnorderedMapToRawPairs(Utils::raw_pair *raw[], const std::unordered_map &map) - { - if (raw && map.size() ) - { - raw_pair *arr = new raw_pair[map.size()]; - - *raw = arr; - - size_t i = 0; - - for (auto it = map.cbegin(); map.cend() != it; ++it, ++i) - { - arr[i].key = stlStringToPChar(it->first); - arr[i].value = stlStringToPChar(it->second); - } - } - } - - void stlUnorderedMultimapToRawPairs(Utils::raw_pair *raw[], const std::unordered_multimap &map) - { - if (raw && map.size() ) - { - raw_pair *arr = new raw_pair[map.size()]; - - *raw = arr; - - size_t i = 0; - - for (auto it = map.cbegin(); map.cend() != it; ++it, ++i) - { - arr[i].key = stlStringToPChar(it->first); - arr[i].value = stlStringToPChar(it->second); - } - } - } - - void filesIncomingToRawFilesInfo(Utils::raw_fileinfo *raw[], const std::unordered_multimap &map) - { - if (raw && map.size() ) - { - raw_fileinfo *arr = new raw_fileinfo[map.size()]; - - *raw = arr; - - size_t i = 0; - - for (auto it = map.cbegin(); map.cend() != it; ++it, ++i) - { - arr[i].key = stlStringToPChar(it->first); - - const HttpServer::FileIncoming &file = it->second; - - arr[i].file_name = stlStringToPChar(file.getName() ); - arr[i].file_type = stlStringToPChar(file.getType() ); - arr[i].file_size = file.getSize(); - } - } - } - - void rawPairsToStlMap(std::map &map, const Utils::raw_pair raw[], const size_t count) - { - for (size_t i = 0; i < count; ++i) - { - map.emplace(raw[i].key ? raw[i].key : "", raw[i].value); - } - } - - void rawPairsToStlUnorderedMap(std::unordered_map &map, const Utils::raw_pair raw[], const size_t count) - { - for (size_t i = 0; i < count; ++i) - { - map.emplace(raw[i].key, raw[i].value); - } - } - - void rawPairsToStlUnorderedMultimap(std::unordered_multimap &map, const Utils::raw_pair raw[], const size_t count) - { - for (size_t i = 0; i < count; ++i) - { - map.emplace(raw[i].key, raw[i].value); - } - } - - void rawFilesInfoToFilesIncoming(std::unordered_multimap &map, const Utils::raw_fileinfo raw[], const size_t count) - { - for (size_t i = 0; i < count; ++i) - { - map.emplace(raw[i].key, HttpServer::FileIncoming(raw[i].file_name, raw[i].file_type, raw[i].file_size) ); - } - } - - void destroyRawPairs(Utils::raw_pair raw[], const size_t count) - { - if (raw) - { - for (size_t i = 0; i < count; ++i) - { - raw_pair &cur = raw[i]; - - delete[] cur.key; - delete[] cur.value; - } - - delete[] raw; - } - } - - void destroyRawFilesInfo(Utils::raw_fileinfo raw[], const size_t count) - { - if (raw) - { - for (size_t i = 0; i < count; ++i) - { - raw_fileinfo &cur = raw[i]; - - delete[] cur.key; - delete[] cur.file_name; - delete[] cur.file_type; - } - - delete[] raw; - } - } - - time_t stringTimeToTimestamp(const std::string &strTime) - { - /* static const std::unordered_map map_days { - {"Sun", 0}, {"Mon", 1}, {"Tue", 2}, {"Wed", 3}, {"Thu", 4}, {"Fri", 5}, {"Sat", 6} - };*/ - - static const std::unordered_map map_months { - {"Jan", 0}, {"Feb", 1}, {"Mar", 2}, {"Apr", 3}, {"May", 4}, {"Jun", 5}, {"Jul", 6}, {"Aug", 7}, {"Sep", 8}, {"Oct", 9}, {"Nov", 10}, {"Dec", 11} - }; - - const size_t str_mon_length = 32; - char *s_mon = new char[str_mon_length]; - ::memset(s_mon, 0, str_mon_length); - - struct ::tm tc = {0}; - - // Parse RFC 822 - #ifdef WIN32 - if (std::numeric_limits::max() != ::sscanf_s(strTime.c_str(), "%*s %d %3s %d %d:%d:%d", &tc.tm_mday, s_mon, str_mon_length, &tc.tm_year, &tc.tm_hour, &tc.tm_min, &tc.tm_sec) ) - #else - if (std::numeric_limits::max() != ::sscanf(strTime.c_str(), "%*s %d %3s %d %d:%d:%d", &tc.tm_mday, s_mon, &tc.tm_year, &tc.tm_hour, &tc.tm_min, &tc.tm_sec) ) - #endif - { - tc.tm_year -= 1900; - - auto it_mon = map_months.find(s_mon); - - if (map_months.end() != it_mon) - { - tc.tm_mon = it_mon->second; - } - } - - delete[] s_mon; - - return ::mktime(&tc); - } - - std::string getDatetimeStringValue(const ::time_t tTime, const bool isGmtTime) - { - char buf[64]; - - ::time_t cur_time = tTime; - - if (std::numeric_limits<::time_t>::max() == tTime) - { - ::time(&cur_time); - } - - #ifdef WIN32 - struct ::tm stm = {0}; - - if (isGmtTime) - { - ::localtime_s(&stm, &cur_time); - } - else - { - ::gmtime_s(&stm, &cur_time); - } - - // RFC 822 - ::strftime(buf, 64, "%a, %d %b %Y %H:%M:%S GMT", &stm); - #else - struct ::tm *ptm = isGmtTime ? localtime(&cur_time) : gmtime(&cur_time); - - // RFC 822 - ::strftime(buf, 64, "%a, %d %b %G %H:%M:%S GMT", ptm); - #endif - - return std::string(buf); - } - - size_t getNumberLength(const size_t number) - { - size_t length = 0; - - size_t n = number; - - do - { - ++length; - n /= 10; - } - while (n); - - return length; - } - - bool parseCookies(const std::string &cookieHeader, std::unordered_multimap &cookies) - { - if (cookieHeader.empty() ) - { - return false; - } - - for (size_t cur_pos = 0, next_value; std::string::npos != cur_pos; cur_pos = next_value) - { - next_value = cookieHeader.find(' ', cur_pos); - - size_t delimiter = cookieHeader.find('=', cur_pos); - - if (std::string::npos == delimiter || delimiter > next_value) - { - return false; - } - - std::string key = cookieHeader.substr(cur_pos, delimiter - cur_pos); - trim(key); - - ++delimiter; - - std::string value = cookieHeader.substr(delimiter, std::string::npos != next_value ? next_value - delimiter : next_value); - trim(value); - - cookies.emplace(std::move(key), std::move(value) ); - - if (std::string::npos != next_value) - { - ++next_value; - } - } - - return true; - } -}; \ No newline at end of file diff --git a/httpserverapp/Utils.h b/httpserverapp/Utils.h deleted file mode 100644 index 103dccb..0000000 --- a/httpserverapp/Utils.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include "RawData.h" -#include "FileIncoming.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Utils -{ - inline void tolower(std::string &str, const std::locale &loc) - { - for (auto &c : str) - { - c = std::tolower(c, loc); - } - } - - void trim(std::string &); - - inline std::string getUniqueName() - { - std::stringstream s; - s << std::hex << std::chrono::high_resolution_clock::now().time_since_epoch().count(); - return s.str(); - } - - char *stlStringToPChar(const std::string &); - - void stlMapToRawPairs(Utils::raw_pair *[], const std::map &); - void stlMultimapToRawPairs(Utils::raw_pair *[], const std::multimap &); - void stlUnorderedMapToRawPairs(Utils::raw_pair *[], const std::unordered_map &); - void stlUnorderedMultimapToRawPairs(Utils::raw_pair *[], const std::unordered_multimap &); - void filesIncomingToRawFilesInfo(Utils::raw_fileinfo *[], const std::unordered_multimap &); - void rawPairsToStlMap(std::map &, const Utils::raw_pair [], const size_t); - void rawPairsToStlMultimap(std::multimap &, const Utils::raw_pair [], const size_t); - void rawPairsToStlUnorderedMap(std::unordered_map &, const Utils::raw_pair [], const size_t); - void rawPairsToStlUnorderedMultimap(std::unordered_multimap &, const Utils::raw_pair [], const size_t); - void rawFilesInfoToFilesIncoming(std::unordered_multimap &, const Utils::raw_fileinfo [], const size_t); - void destroyRawPairs(Utils::raw_pair [], const size_t); - void destroyRawFilesInfo(Utils::raw_fileinfo [], const size_t); - - time_t stringTimeToTimestamp(const std::string &); - - std::string getDatetimeStringValue(const ::time_t = std::numeric_limits<::time_t>::max(), const bool = false); - - size_t getNumberLength(const size_t number); - - bool parseCookies(const std::string &, std::unordered_multimap &); -}; diff --git a/httpserverapp/httpserverapp.cproj b/httpserverapp/httpserverapp.cproj deleted file mode 100644 index 3393b9c..0000000 --- a/httpserverapp/httpserverapp.cproj +++ /dev/null @@ -1,56 +0,0 @@ - - - - Debug - AnyCPU - {4C15CBD7-D1C0-4869-AEE1-14CB41328514} - - - - CPP - Bin - - - true - bin\Debug - httpserverapp - SharedLibrary - DEBUG MONODEVELOP POSIX - . - -std=c++11 - All - - - bin\Release - httpserverapp - SharedLibrary - 3 - MONODEVELOP POSIX - . - -std=c++11 - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/httpserverapp/httpserverapp.md.pc b/httpserverapp/httpserverapp.md.pc deleted file mode 100644 index d109504..0000000 --- a/httpserverapp/httpserverapp.md.pc +++ /dev/null @@ -1,5 +0,0 @@ -Name: httpserverapp -Description: -Version: 0.1 -Libs: -L"/media/projects/httpserverapp/httpserverapp/bin/Release" -lhttpserverapp -Cflags: -I"/media/projects/httpserverapp/httpserverapp" diff --git a/projects/msvs/httpserverapp.sln b/projects/msvs/httpserverapp.sln new file mode 100644 index 0000000..07921f1 --- /dev/null +++ b/projects/msvs/httpserverapp.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Express 2013 for Windows Desktop +VisualStudioVersion = 12.0.40629.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "httpserverapp", "httpserverapp.vcxproj", "{D1565609-DDDE-4521-8AD9-0D1AD7D18525}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Debug|Win32.ActiveCfg = Debug|x64 + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Debug|Win32.Build.0 = Debug|x64 + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Debug|x64.ActiveCfg = Debug|x64 + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Debug|x64.Build.0 = Debug|x64 + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Release|Win32.ActiveCfg = Release|Win32 + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Release|Win32.Build.0 = Release|Win32 + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Release|x64.ActiveCfg = Release|x64 + {D1565609-DDDE-4521-8AD9-0D1AD7D18525}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/projects/msvs/httpserverapp.vcxproj b/projects/msvs/httpserverapp.vcxproj new file mode 100644 index 0000000..cec4a76 --- /dev/null +++ b/projects/msvs/httpserverapp.vcxproj @@ -0,0 +1,222 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {D1565609-DDDE-4521-8AD9-0D1AD7D18525} + Win32Proj + httpserverapp + + + + DynamicLibrary + true + v141 + MultiByte + + + DynamicLibrary + true + v141 + MultiByte + + + DynamicLibrary + false + v141 + true + MultiByte + + + DynamicLibrary + false + v141 + true + MultiByte + + + + + + + + + + + + + + + + + + + true + ..\..\build\$(Platform)\$(Configuration)\ + ..\..\build\$(Platform)\$(Configuration)\ + D:\usr\include;$(IncludePath) + D:\usr\lib;$(LibraryPath) + + + true + ..\..\build\$(Platform)\$(Configuration)\ + ..\..\build\$(Platform)\$(Configuration)\ + D:\usr\include;$(IncludePath) + D:\usr\lib64;$(LibraryPath) + + + false + ..\..\build\$(Platform)\$(Configuration)\ + ..\..\build\$(Platform)\$(Configuration)\ + D:\usr\include;$(IncludePath) + D:\usr\lib;$(LibraryPath) + + + false + ..\..\build\$(Platform)\$(Configuration)\ + ..\..\build\$(Platform)\$(Configuration)\ + D:\usr\include;$(IncludePath) + D:\usr\lib64;$(LibraryPath) + + + + NotUsing + Level3 + Disabled + WIN32;NOMINMAX;DEBUG;%(PreprocessorDefinitions) + $(IntDir)asm\ + $(IntDir)obj\ + true + -Dssize_t=long %(AdditionalOptions) + + + Windows + true + ws2_32.lib;libgnutls.dll.a;%(AdditionalDependencies) + + + + + NotUsing + Level3 + Disabled + WIN32;NOMINMAX;DEBUG;%(PreprocessorDefinitions) + $(IntDir)asm\ + $(IntDir)obj\ + true + -Dssize_t=long %(AdditionalOptions) + + + Windows + true + ws2_32.lib;libgnutls.dll.a;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NOMINMAX;NDEBUG;%(PreprocessorDefinitions) + $(IntDir)asm\ + $(IntDir)obj\ + true + -Dssize_t=long %(AdditionalOptions) + + + Windows + true + true + true + ws2_32.lib;libgnutls.dll.a;%(AdditionalDependencies) + + + + + Level3 + NotUsing + MaxSpeed + true + true + WIN32;NOMINMAX;NDEBUG;%(PreprocessorDefinitions) + $(IntDir)asm\ + $(IntDir)obj\ + true + -Dssize_t=long %(AdditionalOptions) + + + Windows + true + true + true + ws2_32.lib;libgnutls.dll.a;%(AdditionalDependencies) + + + + + + \ No newline at end of file diff --git a/projects/msvs/httpserverapp.vcxproj.filters b/projects/msvs/httpserverapp.vcxproj.filters new file mode 100644 index 0000000..1e4277e --- /dev/null +++ b/projects/msvs/httpserverapp.vcxproj.filters @@ -0,0 +1,147 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/projects/msvs/httpserverapp.vcxproj.user b/projects/msvs/httpserverapp.vcxproj.user new file mode 100644 index 0000000..223f65b --- /dev/null +++ b/projects/msvs/httpserverapp.vcxproj.user @@ -0,0 +1,18 @@ + + + + + + $(ProjectDir) + WindowsLocalDebugger + + + + + $(ProjectDir) + WindowsLocalDebugger + + + true + + \ No newline at end of file diff --git a/projects/qt-creator/httpserverapp.qbs b/projects/qt-creator/httpserverapp.qbs new file mode 100644 index 0000000..56d90fc --- /dev/null +++ b/projects/qt-creator/httpserverapp.qbs @@ -0,0 +1,69 @@ +import qbs + +Project { + DynamicLibrary { + name: "httpserverapp" + + Depends { name: "cpp" } + cpp.cxxLanguageVersion: "c++14" + + cpp.defines: qbs.buildVariant == "debug" ? ["DEBUG"] : base + + cpp.dynamicLibraries: ["gnutls"] + + Properties { + condition: qbs.targetOS.contains("linux") + cpp.defines: outer.concat(["POSIX"]) + cpp.dynamicLibraries: outer.concat(["dl", "pthread"]) + } + Properties { + condition: qbs.targetOS.contains("windows") + cpp.defines: outer.concat(["WIN32", "NOMINMAX"]) + } + + files: [ + "../../src/Init.cpp", + "../../src/Init.h", + "../../src/application/Test.cpp", + "../../src/application/Test.h", + "../../src/server/protocol/ServerHttp1.cpp", + "../../src/server/protocol/ServerHttp1.h", + "../../src/server/protocol/ServerHttp2.cpp", + "../../src/server/protocol/ServerHttp2.h", + "../../src/server/protocol/ServerProtocol.cpp", + "../../src/server/protocol/ServerProtocol.h", + "../../src/server/protocol/WebSocket.cpp", + "../../src/server/protocol/WebSocket.h", + "../../src/utils/Event.cpp", + "../../src/utils/Event.h", + "../../src/transfer/FileIncoming.cpp", + "../../src/transfer/FileIncoming.h", + "../../src/transfer/http2/HPack.cpp", + "../../src/transfer/http2/HPack.h", + "../../src/transfer/http2/Http2.cpp", + "../../src/transfer/http2/Http2.h", + "../../src/transfer/HttpStatusCode.h", + "../../src/Main.cpp", + "../../src/Main.h", + "../../src/server/Request.cpp", + "../../src/server/Request.h", + "../../src/server/Response.cpp", + "../../src/server/Response.h", + "../../src/transfer/ProtocolVariant.h", + "../../src/transfer/AppRequest.h", + "../../src/transfer/AppResponse.h", + "../../src/socket/Socket.cpp", + "../../src/socket/Socket.h", + "../../src/socket/Adapter.cpp", + "../../src/socket/Adapter.h", + "../../src/socket/AdapterDefault.cpp", + "../../src/socket/AdapterDefault.h", + "../../src/socket/AdapterTls.cpp", + "../../src/socket/AdapterTls.h", + "../../src/system/System.cpp", + "../../src/system/System.h", + "../../src/utils/Utils.cpp", + "../../src/utils/Utils.h", + ] + } +} diff --git a/src/Init.cpp b/src/Init.cpp new file mode 100644 index 0000000..0831bdb --- /dev/null +++ b/src/Init.cpp @@ -0,0 +1,365 @@ + +#include "Init.h" + +#include "socket/AdapterDefault.h" + +#include "transfer/FileIncoming.h" +#include "transfer/http2/Http2.h" +#include "utils/Utils.h" + +#include "server/protocol/ServerHttp1.h" +#include "server/protocol/ServerHttp2.h" + +#include +#include + +Socket::Adapter *createSocketAdapter( + Transfer::app_request *request, + void *addr +) { + if (request->tls_session) { + return new (addr) Socket::AdapterTls(request->tls_session); + } + + return new (addr) Socket::AdapterDefault(request->socket); +} + +void destroySocketAdapter(Socket::Adapter *adapter) { + if (adapter) { + adapter->~Adapter(); + } +} + +std::string utf8ToLocal(const std::string &u8str) +{ + std::locale loc(""); + + std::wstring_convert > conv; + std::wstring wstr = conv.from_bytes(u8str); + + std::string str(wstr.size(), 0); + + std::use_facet >(loc).narrow( + wstr.data(), + wstr.data() + wstr.size(), + '?', + &str.front() + ); + + return str; +} + +std::string getClearPath(const std::string &path) +{ + const size_t pos = path.find_first_of("?#"); + + const std::string clean = Utils::urlDecode( + std::string::npos == pos + ? path + : path.substr(0, pos) + ); + +#ifdef WIN32 + return utf8ToLocal(clean); +#else + return clean; +#endif +} + +static void getIncomingVars( + std::unordered_multimap ¶ms, + const std::string &uri +) { + const size_t start = uri.find('?'); + + if (std::string::npos == start) { + return; + } + + const size_t finish = uri.find('#'); + + if (finish < start) { + return; + } + + for ( + size_t var_pos = start + 1, var_end = 0; + std::string::npos != var_end; + var_pos = var_end + 1 + ) { + var_end = uri.find('&', var_pos); + + if (var_end > finish) { + var_end = std::string::npos; + } + + size_t delimiter = uri.find('=', var_pos); + + if (delimiter >= var_end) { + std::string var_name = Utils::urlDecode( + uri.substr( + var_pos, + std::string::npos != var_end + ? var_end - var_pos + : std::string::npos + ) + ); + + params.emplace( + std::move(var_name), + std::string() + ); + } else { + std::string var_name = Utils::urlDecode( + uri.substr( + var_pos, + delimiter - var_pos + ) + ); + + ++delimiter; + + std::string var_value = Utils::urlDecode( + uri.substr( + delimiter, + std::string::npos != var_end + ? var_end - delimiter + : std::string::npos + ) + ); + + params.emplace( + std::move(var_name), + std::move(var_value) + ); + } + } +} + +bool initServerObjects( + HttpServer::Request *procRequest, + HttpServer::Response *procResponse, + const Transfer::app_request *request, + Socket::Adapter *socket_adapter +) { + const uint8_t *src = reinterpret_cast(request->request_data); + + size_t protocol_number; + src = Utils::unpackNumber(&protocol_number, src); + Transfer::ProtocolVariant protocol_variant = static_cast(protocol_number); + HttpServer::ServerProtocol *prot = nullptr; + + std::string document_root; + std::string host; + std::string path; + std::string method; + std::unordered_multimap params; + std::unordered_multimap headers; + std::unordered_multimap data; + std::unordered_multimap files; + std::unordered_multimap cookies; + + bool success = true; + + switch (protocol_variant) + { + case Transfer::ProtocolVariant::HTTP_1: + { + src = Utils::unpackString(document_root, src); + src = Utils::unpackString(host, src); + src = Utils::unpackString(path, src); + src = Utils::unpackString(method, src); + src = Utils::unpackContainer(headers, src); + src = Utils::unpackContainer(data, src); + src = Utils::unpackFilesIncoming(files, src); + + auto const it_cookie = headers.find("cookie"); + + if (headers.cend() != it_cookie) { + Utils::parseCookies(it_cookie->second, cookies); + } + + getIncomingVars(params, path); + + prot = new HttpServer::ServerHttp1(socket_adapter); + + break; + } + + case Transfer::ProtocolVariant::HTTP_2: + { + src = Utils::unpackString(document_root, src); + src = Utils::unpackString(host, src); + src = Utils::unpackString(path, src); + src = Utils::unpackString(method, src); + + size_t number; + + src = Utils::unpackNumber(&number, src); + const uint32_t stream_id = static_cast(number); + + Http2::ConnectionSettings settings; + + src = Utils::unpackNumber(&number, src); + settings.header_table_size = static_cast(number); + src = Utils::unpackNumber(&number, src); + settings.enable_push = static_cast(number); + src = Utils::unpackNumber(&number, src); + settings.max_concurrent_streams = static_cast(number); + src = Utils::unpackNumber(&number, src); + settings.initial_window_size = static_cast(number); + src = Utils::unpackNumber(&number, src); + settings.max_frame_size = static_cast(number); + src = Utils::unpackNumber(&number, src); + settings.max_header_list_size = static_cast(number); + + std::deque > dynamic_table; + src = Utils::unpackVector(dynamic_table, src); + + std::mutex *mtx = nullptr; + src = Utils::unpackPointer(reinterpret_cast(&mtx), src); + + src = Utils::unpackContainer(headers, src); + src = Utils::unpackContainer(data, src); + src = Utils::unpackFilesIncoming(files, src); + + auto const it_cookie = headers.find("cookie"); + + if (headers.cend() != it_cookie) { + Utils::parseCookies(it_cookie->second, cookies); + } + + getIncomingVars(params, path); + + Http2::OutStream *stream = new Http2::OutStream( + stream_id, + settings, + Http2::DynamicTable( + settings.header_table_size, + settings.max_header_list_size, + std::move(dynamic_table) + ), + mtx + ); + + prot = new HttpServer::ServerHttp2(socket_adapter, stream); + + break; + } + + default: { + success = false; + break; + } + } + + *procRequest = HttpServer::Request { + prot, + std::move(document_root), + std::move(host), + std::move(path), + std::move(method), + std::move(params), + std::move(headers), + std::move(data), + std::move(files), + std::move(cookies), + protocol_variant + }; + + *procResponse = HttpServer::Response { + prot, + protocol_variant, + std::unordered_map(), + Http::StatusCode::EMPTY + }; + + return success; +} + +void freeProtocolData(HttpServer::Response *response) { + if (response) { + delete response->prot; + } +} + +bool isSwitchingProtocols( + const HttpServer::Request &request, + HttpServer::Response &response +) { + // Check for https is not set + if (request.prot->getSocket()->get_tls_session() != nullptr) { + return false; + } + + // Check for upgrade to https + /*auto const it_upgrade_insecure = request.headers.find("upgrade-insecure-requests"); + + if (request.headers.cend() != it_upgrade_insecure) { + if (it_upgrade_insecure->second == "1") { + response.status = Http::StatusCode::MOVED_TEMPORARILY; + response.headers["location"] = "https://" + request.host + request.path; + response.headers["strict-transport-security"] = "max-age=86400"; + + const std::string headers = "HTTP/1.1 307 Moved Temporarily\r\nLocation: https://" + request.host + request.path + "\r\nStrict-Transport-Security: max-age=86400\r\n\r\n"; + + response.prot->getSocket()->nonblock_send(headers, std::chrono::milliseconds(5000) ); + + return true; + } + }*/ + + // Check if switch protocol to h2c + auto const it_upgrade = request.headers.find("upgrade"); + + if (request.headers.cend() == it_upgrade) { + return false; + } + + auto const it_connection = request.headers.find("connection"); + + if (request.headers.cend() == it_connection) { + return false; + } + + std::vector list = Utils::explode(it_connection->second, ','); + + bool is_upgrade = false; + + for (auto &item : list) { + Utils::toLower(item); + + if ("upgrade" == item) { + is_upgrade = true; + break; + } + } + + if (false == is_upgrade) { + return false; + } + + const std::string &upgrade = it_upgrade->second; + + if ("h2c" != upgrade) { + return false; + } + + auto const it_settings = request.headers.find("http2-settings"); + + if (request.headers.cend() == it_settings) { + return false; + } + + response.headers["connection"] = "upgrade"; + response.headers["upgrade"] = "h2c"; + + const std::string headers = "HTTP/1.1 101 Switching Protocols\r\nConnection: Upgrade\r\nUpgrade: h2c\r\n\r\n"; + + response.prot->getSocket()->nonblock_send( + headers, + std::chrono::milliseconds(5000) + ); + + return true; +} diff --git a/src/Init.h b/src/Init.h new file mode 100644 index 0000000..b7a4c0b --- /dev/null +++ b/src/Init.h @@ -0,0 +1,29 @@ +#pragma once + +#include "server/Request.h" +#include "server/Response.h" +#include "transfer/AppRequest.h" +#include "transfer/AppResponse.h" + +Socket::Adapter *createSocketAdapter( + Transfer::app_request *request, + void *addr +); + +void destroySocketAdapter(Socket::Adapter *adapter); + +std::string getClearPath(const std::string &path); + +bool initServerObjects( + HttpServer::Request *procRequest, + HttpServer::Response *procResponse, + const Transfer::app_request *request, + Socket::Adapter *socket_adapter +); + +void freeProtocolData(HttpServer::Response *response); + +bool isSwitchingProtocols( + const HttpServer::Request &request, + HttpServer::Response &response +); diff --git a/src/Main.cpp b/src/Main.cpp new file mode 100644 index 0000000..270689e --- /dev/null +++ b/src/Main.cpp @@ -0,0 +1,80 @@ + +#include "Main.h" +#include "Init.h" + +#include "application/Test.h" + +#include "utils/Utils.h" + +EXPORT bool application_init(const char *root) +{ + return true; +} + +EXPORT int application_call( + Transfer::app_request *request, + Transfer::app_response *response +) { + // Allocate memory on the stack + uint8_t addr[sizeof(Socket::AdapterTls)]; + + // Create the socket adapter + Socket::Adapter *socket_adapter = createSocketAdapter(request, addr); + + HttpServer::Request proc_request; + HttpServer::Response proc_response; + + if (initServerObjects(&proc_request, &proc_response, request, socket_adapter) == false) { + return EXIT_FAILURE; + } + + const std::string absolute_path = proc_request.document_root + getClearPath(proc_request.path); + + int result = EXIT_SUCCESS; + + if (isSwitchingProtocols(proc_request, proc_response) ) { + + } else if ( + std::string::npos == absolute_path.find("/../") && + System::isFileExists(absolute_path) + ) { + auto it_connection = proc_request.headers.find("connection"); + + if (proc_request.headers.cend() != it_connection) { + proc_response.headers["connection"] = it_connection->second; + } + + proc_response.headers["x-sendfile"] = absolute_path; + } else { + // Call application + result = Application::test(proc_request, proc_response); + } + + destroySocketAdapter(socket_adapter); + + if (proc_response.headers.size() ) { + response->data_size = Utils::getPackContainerSize(proc_response.headers); + response->response_data = new uint8_t[response->data_size]; + + Utils::packContainer( + reinterpret_cast(response->response_data), + proc_response.headers + ); + } + + freeProtocolData(&proc_response); + + return result; +} + +EXPORT void application_clear(void *response_data, size_t response_size) +{ + if (response_data && response_size) { + delete[] reinterpret_cast(response_data); + } +} + +EXPORT void application_final(const char *root) +{ + +} diff --git a/src/Main.h b/src/Main.h new file mode 100644 index 0000000..ba712da --- /dev/null +++ b/src/Main.h @@ -0,0 +1,7 @@ +#pragma once + +#ifdef _MSC_VER + #define EXPORT extern "C" __declspec(dllexport) +#else + #define EXPORT extern "C" +#endif diff --git a/src/application/Test.cpp b/src/application/Test.cpp new file mode 100644 index 0000000..78249e0 --- /dev/null +++ b/src/application/Test.cpp @@ -0,0 +1,148 @@ + +#include "Test.h" + +#include "../utils/Utils.h" + +namespace Application +{ + bool test( + HttpServer::Request &request, + HttpServer::Response &response + ) { + // Output incoming headers + + std::string s = R"( + + + Incoming headers + + + Header name + Header value + + + +)"; + + for (auto const &pair : request.headers) + { + s += R"( + )" + pair.first + R"( + )" + pair.second + R"( + +)"; + } + + // Output incoming url parameters + + s += R"( + + + Incoming url parameters + + + Parameter name + Parameter value + + + +)"; + + for (auto const &pair : request.params) + { + s += R"( + )" + pair.first + R"( + )" + pair.second + R"( + +)"; + } + + // Output incoming form data + + s += R"( + + + Incoming form data + + + Form field + Field value + + + +)"; + + for (auto const &pair : request.data) + { + s += R"( + )" + pair.first + R"( + )" + pair.second + R"( + +)"; + } + + // Output info about incoming files + + s += R"( + + + Incoming files + + + Form field + Original name + File type + File size + Uploaded name + + + +)"; + + for (auto const &pair : request.files) + { + const std::string &field_name = pair.first; + const Transfer::FileIncoming &file = pair.second; + + s += R"( + )" + field_name + R"( + )" + file.getName() + R"( + )" + file.getType() + R"( + )" + std::to_string(file.getSize() ) + R"( + )" + file.getTmpName() + R"( + +)"; + } + + s += R"( +)"; + + std::unordered_map &headers = response.headers; + + // Set outgoing headers + + response.setStatusCode(Http::StatusCode::OK); + headers["content-type"] = "text/html; charset=utf-8"; + headers["accept-ranges"] = "bytes"; + headers["content-length"] = std::to_string(s.size() ); + headers["connection"] = "keep-alive"; + headers["date"] = Utils::getDatetimeAsString(); + + // Additional headers + // In additional headers may be placed values of cookies + std::vector > additional; + // additional.emplace_back("set-cookie", "key=value; expires=; path=/; domain=*"); + + // Send headers and data + + const std::chrono::milliseconds timeout(5000); + + if (response.sendHeaders(additional, timeout, s.empty() ) ) { + if (s.empty() == false) { + response.sendData(s.data(), s.size(), timeout, true); + } + } + + return EXIT_SUCCESS; + } +} diff --git a/src/application/Test.h b/src/application/Test.h new file mode 100644 index 0000000..d6a6316 --- /dev/null +++ b/src/application/Test.h @@ -0,0 +1,12 @@ +#pragma once + +#include "../server/Request.h" +#include "../server/Response.h" + +namespace Application +{ + bool test( + HttpServer::Request &, + HttpServer::Response & + ); +} diff --git a/src/server/Request.cpp b/src/server/Request.cpp new file mode 100644 index 0000000..c78934a --- /dev/null +++ b/src/server/Request.cpp @@ -0,0 +1,75 @@ + +#include "Request.h" + +namespace HttpServer +{ + std::string Request::getHeader(const std::string &key) const { + auto it = headers.find(key); + return headers.end() != it ? it->second : std::string(); + } + + bool Request::isDataExists(const std::string &key) const { + return data.cend() != data.find(key); + } + + std::string Request::getDataAsString(const std::string &key) const { + auto it = data.find(key); + return data.end() != it ? it->second : std::string(); + } + + std::vector Request::getDataAsArray(const std::string &key) const + { + std::vector arr; + + size_t count = data.count(key); + + if (count) { + auto range = data.equal_range(key); + + arr.resize(count); + + size_t i = 0; + + for (auto it = range.first; it != range.second; ++it) { + arr[i++] = it->second; + } + } + + return arr; + } + + bool Request::isFileExists(const std::string &key) const { + return this->files.cend() != this->files.find(key); + } + + Transfer::FileIncoming Request::getFile(const std::string &key) const { + auto const it = this->files.find(key); + return this->files.end() != it ? it->second : Transfer::FileIncoming(); + } + + std::vector Request::getFilesAsArray(const std::string &key) const + { + std::vector arr; + + const size_t count = this->files.count(key); + + if (count) { + auto const range = this->files.equal_range(key); + + arr.resize(count); + + size_t i = 0; + + for (auto it = range.first; it != range.second; ++it) { + arr[i++] = it->second; + } + } + + return arr; + } + + std::string Request::getCookieAsString(const std::string &cookieName) const { + auto it = cookies.find(cookieName); + return cookies.end() != it ? it->second : std::string(); + } +} diff --git a/src/server/Request.h b/src/server/Request.h new file mode 100644 index 0000000..1255c65 --- /dev/null +++ b/src/server/Request.h @@ -0,0 +1,56 @@ +#pragma once + +#include "protocol/ServerProtocol.h" +#include "../transfer/FileIncoming.h" +#include "../transfer/ProtocolVariant.h" + +#include + +namespace HttpServer +{ + /** + * Структура запроса (входные данные) + * + * @member const Socket::Adapter &socket - сокет клиента + * @member const std::string method - метод применяемый к ресурсу + * @member const std::string uri_reference - ссылка на ресурс + * @member const std::string document_root - корневая директория приложения + * @member const std::unordered_multimap params - параметры ресурса + * @member const std::unordered_map headers - заголовки запроса + * @member const std::unordered_multimap data - входящие данные запроса + * @member const std::unordered_multimap files - входящие файлы запроса + * @member const std::unordered_multimap cookies - входящие куки запроса + */ + struct Request + { + ServerProtocol *prot; + std::string document_root; + std::string host; + std::string path; + std::string method; + std::unordered_multimap params; + std::unordered_multimap headers; + std::unordered_multimap data; + std::unordered_multimap files; + std::unordered_multimap cookies; + + Transfer::ProtocolVariant protocol_variant; + + public: + std::string getHeader(const std::string &key) const; + + bool isDataExists(const std::string &key) const; + + std::string getDataAsString(const std::string &key) const; + + std::vector getDataAsArray(const std::string &key) const; + + bool isFileExists(const std::string &key) const; + + Transfer::FileIncoming getFile(const std::string &key) const; + + std::vector getFilesAsArray(const std::string &key) const; + + std::string getCookieAsString(const std::string &cookieName) const; + }; +} diff --git a/src/server/Response.cpp b/src/server/Response.cpp new file mode 100644 index 0000000..27ec2c6 --- /dev/null +++ b/src/server/Response.cpp @@ -0,0 +1,60 @@ + +#include "Response.h" + +#include "../transfer/http2/HPack.h" + +#include + +namespace HttpServer +{ + void Response::setStatusCode( + const Http::StatusCode status + ) noexcept { + this->status = status; + } + + bool Response::sendHeaders( + const std::vector > &additional, + const std::chrono::milliseconds &timeout, + const bool endStream + ) const { + std::vector > headers; + + headers.reserve( + this->headers.size() + additional.size() + ); + + headers.insert( + headers.end(), + this->headers.cbegin(), + this->headers.cend() + ); + + headers.insert( + headers.end(), + additional.cbegin(), + additional.cend() + ); + + return this->prot->sendHeaders( + this->status, + headers, + timeout, + endStream + ); + } + + long Response::sendData( + const void *src, + const size_t size, + const std::chrono::milliseconds &timeout, + const bool endStream + ) const noexcept { + return this->prot->sendData( + src, + size, + timeout, + endStream + ); + } +} diff --git a/src/server/Response.h b/src/server/Response.h new file mode 100644 index 0000000..79ad7a8 --- /dev/null +++ b/src/server/Response.h @@ -0,0 +1,35 @@ +#pragma once + +#include "protocol/ServerProtocol.h" +#include "../transfer/ProtocolVariant.h" +#include "../transfer/HttpStatusCode.h" + +#include +#include + +namespace HttpServer +{ + struct Response + { + ServerProtocol *prot; + Transfer::ProtocolVariant protocol_variant; + std::unordered_map headers; + Http::StatusCode status; + + public: + void setStatusCode(const Http::StatusCode status) noexcept; + + bool sendHeaders( + const std::vector > &additional, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const; + + long sendData( + const void *src, + const size_t size, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const noexcept; + }; +} diff --git a/src/server/protocol/ServerHttp1.cpp b/src/server/protocol/ServerHttp1.cpp new file mode 100644 index 0000000..350f7c3 --- /dev/null +++ b/src/server/protocol/ServerHttp1.cpp @@ -0,0 +1,41 @@ + +#include "ServerHttp1.h" + +namespace HttpServer +{ + ServerHttp1::ServerHttp1(Socket::Adapter *sock) noexcept + : ServerProtocol(sock) + { + + } + + bool ServerHttp1::sendHeaders( + const Http::StatusCode status, + std::vector > &headers, + const std::chrono::milliseconds &timeout, + const bool endStream + ) const { + std::string out = "HTTP/1.1 " + std::to_string(static_cast(status) ) + "\r\n"; + + for (auto const &h : headers) { + out += h.first + ": " + h.second + "\r\n"; + } + + out += "\r\n"; + + return this->sock->nonblock_send(out, timeout) > 0; + } + + long ServerHttp1::sendData( + const void *src, + const size_t size, + const std::chrono::milliseconds &timeout, + const bool endStream + ) const noexcept { + return this->sock->nonblock_send( + src, + size, + timeout + ); + } +} diff --git a/src/server/protocol/ServerHttp1.h b/src/server/protocol/ServerHttp1.h new file mode 100644 index 0000000..55b3b08 --- /dev/null +++ b/src/server/protocol/ServerHttp1.h @@ -0,0 +1,26 @@ +#pragma once + +#include "ServerProtocol.h" + +namespace HttpServer +{ + class ServerHttp1 : public ServerProtocol + { + public: + ServerHttp1(Socket::Adapter *sock) noexcept; + + virtual bool sendHeaders( + const Http::StatusCode status, + std::vector > &headers, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const override; + + virtual long sendData( + const void *src, + const size_t size, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const noexcept override; + }; +} diff --git a/src/server/protocol/ServerHttp2.cpp b/src/server/protocol/ServerHttp2.cpp new file mode 100644 index 0000000..fd64c82 --- /dev/null +++ b/src/server/protocol/ServerHttp2.cpp @@ -0,0 +1,239 @@ + +#include "ServerHttp2.h" + +#include "../../transfer/http2/HPack.h" + +#include +#include + +namespace HttpServer +{ + ServerHttp2::ServerHttp2( + Socket::Adapter *sock, + Http2::OutStream *stream + ) noexcept + : ServerProtocol(sock), stream(stream) + { + + } + + ServerHttp2::~ServerHttp2() noexcept { + delete this->stream; + } + + static uint8_t getPaddingSize(const size_t dataSize) + { + if (0 == dataSize) { + return 0; + } + + std::random_device rd; + + uint8_t padding = uint8_t(rd()); + + while (dataSize <= padding) { + padding /= 2; + } + + return padding; + } + + bool ServerHttp2::sendHeaders( + const Http::StatusCode status, + std::vector > &headers, + const std::chrono::milliseconds &timeout, + const bool endStream + ) const { + std::vector buf; + buf.reserve(4096); + + buf.resize( + Http2::FRAME_HEADER_SIZE + sizeof(uint8_t) + ); + + headers.emplace( + headers.begin(), + ":status", + std::to_string(static_cast(status) ) + ); + + HPack::pack(buf, headers, this->stream->dynamic_table); + + size_t data_size = ( + buf.size() - Http2::FRAME_HEADER_SIZE - sizeof(uint8_t) + ); + + const uint8_t padding = getPaddingSize(data_size); + const uint16_t padding_size = padding + sizeof(uint8_t); + + if (data_size + padding_size > this->stream->settings.max_frame_size) { + data_size = this->stream->settings.max_frame_size - padding_size; + } + + const uint32_t frame_size = static_cast( + data_size + padding_size + ); + + buf.resize( + frame_size + Http2::FRAME_HEADER_SIZE + ); + + Http2::FrameFlag flags = Http2::FrameFlag::END_HEADERS; + + if (endStream) { + flags |= Http2::FrameFlag::END_STREAM; + } + + flags |= Http2::FrameFlag::PADDED; + + buf[Http2::FRAME_HEADER_SIZE] = char(padding); + + if (padding) { + std::fill( + buf.end() - padding, + buf.end(), + 0 + ); + } + + this->stream->setHttp2FrameHeader( + reinterpret_cast(buf.data() ), + frame_size, + Http2::FrameType::HEADERS, + flags + ); + + const std::unique_lock lock(*this->stream->mtx); + + return this->sock->nonblock_send(buf.data(), buf.size(), timeout) > 0; + } + + void ServerHttp2::sendWindowUpdate( + const uint32_t size, + const std::chrono::milliseconds &timeout + ) const { + std::array buf; + uint8_t *addr = buf.data(); + + addr = this->stream->setHttp2FrameHeader( + addr, + sizeof(uint32_t), + Http2::FrameType::WINDOW_UPDATE, + Http2::FrameFlag::EMPTY + ); + + *reinterpret_cast(addr) = ::htonl(size); + + const std::unique_lock lock(*this->stream->mtx); + + this->sock->nonblock_send(buf.data(), buf.size(), timeout); + } + + long ServerHttp2::sendData( + const void *src, + const size_t size, + const std::chrono::milliseconds &timeout, + const bool endStream + ) const noexcept { + const uint8_t *data = reinterpret_cast(src); + + std::vector buf; + + buf.reserve( + this->stream->settings.max_frame_size + Http2::FRAME_HEADER_SIZE + ); + + size_t total = 0; + + while (total < size) + { + size_t data_size = (size - total < this->stream->settings.max_frame_size) + ? size - total + : this->stream->settings.max_frame_size; + + const uint8_t padding = getPaddingSize(data_size); + const uint16_t padding_size = padding + sizeof(uint8_t); + + if (data_size + padding_size > this->stream->settings.max_frame_size) { + data_size = this->stream->settings.max_frame_size - padding_size; + } + + const uint32_t frame_size = static_cast( + data_size + padding_size + ); + + buf.resize(frame_size + Http2::FRAME_HEADER_SIZE); + + if (this->stream->window_size_out - long(this->stream->settings.max_frame_size) <= 0) + { + size_t update_size = this->stream->settings.initial_window_size + + (size - total) - size_t(this->stream->window_size_out); + + if (update_size > Http2::MAX_WINDOW_UPDATE) { + update_size = Http2::MAX_WINDOW_UPDATE; + } + + this->sendWindowUpdate(uint32_t(update_size), timeout); + + this->stream->window_size_out += update_size; + } + + Http2::FrameFlag flags = Http2::FrameFlag::EMPTY; + + if (endStream && (total + data_size >= size) ) { + flags |= Http2::FrameFlag::END_STREAM; + } + + size_t cur = Http2::FRAME_HEADER_SIZE; + + if (padding_size) { + flags |= Http2::FrameFlag::PADDED; + buf[cur] = padding; + ++cur; + } + + this->stream->setHttp2FrameHeader( + buf.data(), + frame_size, + Http2::FrameType::DATA, + flags + ); + + std::copy( + data, + data + data_size, + buf.data() + cur + ); + + if (padding) { + std::fill( + buf.end() - padding, + buf.end(), + 0 + ); + } + + this->stream->lock(); + + const long sended = this->sock->nonblock_send( + buf.data(), + buf.size(), + timeout + ); + + this->stream->unlock(); + + if (sended <= 0) { + total = 0; + break; + } + + this->stream->window_size_out -= frame_size; + + data += data_size; + total += data_size; + } + + return long(total); + } +} diff --git a/src/server/protocol/ServerHttp2.h b/src/server/protocol/ServerHttp2.h new file mode 100644 index 0000000..b7389f1 --- /dev/null +++ b/src/server/protocol/ServerHttp2.h @@ -0,0 +1,41 @@ +#pragma once + +#include "ServerProtocol.h" + +#include "../../transfer/http2/Http2.h" + +namespace HttpServer +{ + class ServerHttp2 : public ServerProtocol + { + protected: + Http2::OutStream *stream; + + public: + ServerHttp2( + Socket::Adapter *sock, + Http2::OutStream *stream + ) noexcept; + + virtual ~ServerHttp2() noexcept override; + + void sendWindowUpdate( + const uint32_t size, + const std::chrono::milliseconds &timeout + ) const; + + virtual bool sendHeaders( + const Http::StatusCode status, + std::vector > &headers, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const override; + + virtual long sendData( + const void *src, + const size_t size, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const noexcept override; + }; +} diff --git a/src/server/protocol/ServerProtocol.cpp b/src/server/protocol/ServerProtocol.cpp new file mode 100644 index 0000000..d5ac1dd --- /dev/null +++ b/src/server/protocol/ServerProtocol.cpp @@ -0,0 +1,19 @@ + +#include "ServerProtocol.h" + +namespace HttpServer +{ + ServerProtocol::ServerProtocol(Socket::Adapter *sock) noexcept + : sock(sock) + { + + } + + Socket::Adapter *ServerProtocol::getSocket() noexcept { + return this->sock; + } + + void ServerProtocol::close() noexcept { + this->sock->close(); + } +} diff --git a/src/server/protocol/ServerProtocol.h b/src/server/protocol/ServerProtocol.h new file mode 100644 index 0000000..f0c211c --- /dev/null +++ b/src/server/protocol/ServerProtocol.h @@ -0,0 +1,35 @@ +#pragma once + +#include "../../socket/Adapter.h" +#include "../../transfer/HttpStatusCode.h" + +namespace HttpServer +{ + class ServerProtocol + { + protected: + Socket::Adapter *sock; + + public: + ServerProtocol(Socket::Adapter *sock) noexcept; + virtual ~ServerProtocol() noexcept = default; + + Socket::Adapter *getSocket() noexcept; + + virtual bool sendHeaders( + const Http::StatusCode status, + std::vector > &headers, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const = 0; + + virtual long sendData( + const void *src, + const size_t size, + const std::chrono::milliseconds &timeout, + const bool endStream = true + ) const noexcept = 0; + + virtual void close() noexcept; + }; +} diff --git a/src/server/protocol/WebSocket.cpp b/src/server/protocol/WebSocket.cpp new file mode 100644 index 0000000..58bffd4 --- /dev/null +++ b/src/server/protocol/WebSocket.cpp @@ -0,0 +1,161 @@ + +#include "WebSocket.h" + +#include "../../utils/Utils.h" +#include "../../socket/Socket.h" + +#ifdef POSIX + #include +#endif + +namespace HttpClient +{ + WebSocket::WebSocket(const WebSocket &obj) noexcept : sock(obj.sock) {} + + WebSocket::WebSocket(Socket::Adapter *adapter) noexcept : sock(adapter) {} + + std::vector WebSocket::packDataToMessageFrame(const void *data, const size_t size) + { + std::vector frame; + + if (0 == size) { + return frame; + } + + constexpr size_t header_max_size = 14; + frame.reserve(size + header_max_size); + frame.resize(header_max_size); + + frame[0] = 0x81; + + size_t cur_pos = sizeof(uint8_t) * 2; + + if (size <= 125) { + frame[1] = size; + } + else if (size <= 65536) { + frame[1] = 126; + + *reinterpret_cast(&frame[2]) = htons(size); + + cur_pos += sizeof(uint16_t); + } else { // More + frame[1] = 127; + + *reinterpret_cast(&frame[2]) = Utils::hton64(size); + + cur_pos += sizeof(uint64_t); + } + + frame.erase(frame.cbegin() + cur_pos, frame.cend() ); + + frame.insert(frame.cend(), reinterpret_cast(data), reinterpret_cast(data) + size); + + return frame; + } + + Socket::Adapter *WebSocket::getSocket() noexcept { + return this->sock; + } + + const Socket::Adapter *WebSocket::getSocket() const noexcept { + return this->sock; + } + + long WebSocket::nonblock_recv(std::vector &frame, const std::chrono::milliseconds &timeout) const + { + std::vector buf(4096); + + const long recv_size = this->sock->nonblock_recv(buf, timeout); + + if (recv_size <= 0) { + return recv_size; + } + + // See @link: http://tools.ietf.org/html/rfc6455#page-28 + + const uint8_t info_frame = buf[0]; + + if ( (info_frame & 0x08) == 0x08) { // opcode 0x08 — close connection + return -1; + } + + uint8_t info_size = buf[1]; + + const bool is_mask_set = info_size & uint8_t(1 << 7); + + info_size &= ~uint8_t(1 << 7); // Unset first bit + + size_t cur_pos = sizeof(uint8_t) * 2; + + uint64_t frame_size; + + if (info_size <= 125) { + frame_size = info_size; + } + else if (info_size == 126) { + frame_size = ntohs(*reinterpret_cast(&buf[cur_pos]) ); + cur_pos += sizeof(uint16_t); + } else { // if (info_size == 127) + frame_size = Utils::ntoh64(*reinterpret_cast(&buf[cur_pos]) ); + cur_pos += sizeof(uint64_t); + } + + if (frame_size > (buf.size() - cur_pos) ) { + return -1; // Close connection + } + + uint32_t mask; + + if (is_mask_set) { + mask = *reinterpret_cast(&buf[cur_pos]); + cur_pos += sizeof(uint32_t); + } + + frame.reserve(recv_size - cur_pos); + + frame.assign(buf.cbegin() + cur_pos, buf.cbegin() + recv_size); + + if (is_mask_set) { + const size_t aligned = frame.size() - (frame.size() % sizeof(mask)); + + uint32_t * const addr = reinterpret_cast(frame.data() ); + + for (size_t i = 0; i < aligned / sizeof(uint32_t); ++i) { + addr[i] ^= mask; + } + + for (size_t i = aligned; i < frame.size(); ++i) { + frame[i] ^= reinterpret_cast(&mask)[i % sizeof(mask)]; + } + } + + return recv_size - cur_pos; + } + + long WebSocket::nonblock_send(const void *data, const size_t length, const std::chrono::milliseconds &timeout) const + { + const std::vector frame = WebSocket::packDataToMessageFrame(data, length); + + if (frame.empty() ) { + return 0; + } + + return this->sock->nonblock_send(frame.data(), frame.size(), timeout); + } + + long WebSocket::nonblock_send(const std::string &str, const std::chrono::milliseconds &timeout) const + { + const std::vector frame = WebSocket::packDataToMessageFrame(str.data(), str.length() ); + + if (frame.empty() ) { + return 0; + } + + return this->sock->nonblock_send(frame.data(), frame.size(), timeout); + } + + void WebSocket::close() noexcept { + this->sock->close(); + } +} diff --git a/src/server/protocol/WebSocket.h b/src/server/protocol/WebSocket.h new file mode 100644 index 0000000..ad1a672 --- /dev/null +++ b/src/server/protocol/WebSocket.h @@ -0,0 +1,30 @@ +#pragma once + +#include "../../socket/Adapter.h" + +namespace HttpClient +{ + class WebSocket + { + private: + Socket::Adapter *sock; + + public: + static std::vector packDataToMessageFrame(const void *data, const size_t size); + + public: + WebSocket() = delete; + WebSocket(const WebSocket &obj) noexcept; + WebSocket(Socket::Adapter *adapter) noexcept; + + Socket::Adapter *getSocket() noexcept; + const Socket::Adapter *getSocket() const noexcept; + + long nonblock_recv(std::vector &frame, const std::chrono::milliseconds &timeout) const; + + long nonblock_send(const void *data, const size_t length, const std::chrono::milliseconds &timeout) const; + long nonblock_send(const std::string &str, const std::chrono::milliseconds &timeout) const; + + void close() noexcept; + }; +} diff --git a/src/socket/Adapter.cpp b/src/socket/Adapter.cpp new file mode 100644 index 0000000..c99bee6 --- /dev/null +++ b/src/socket/Adapter.cpp @@ -0,0 +1,35 @@ + +#include "Adapter.h" + +namespace Socket +{ + long Adapter::nonblock_recv( + std::vector &buf, + const std::chrono::milliseconds &timeout + ) const noexcept { + return this->nonblock_recv( + buf.data(), + buf.size(), + timeout + ); + } + + long Adapter::nonblock_send( + const std::string &buf, + const std::chrono::milliseconds &timeout + ) const noexcept { + return this->nonblock_send( + buf.data(), + buf.length(), + timeout + ); + } + + bool Adapter::operator ==(const Adapter &obj) const noexcept { + return this->get_handle() == obj.get_handle(); + } + + bool Adapter::operator !=(const Adapter &obj) const noexcept { + return this->get_handle() != obj.get_handle(); + } +} diff --git a/src/socket/Adapter.h b/src/socket/Adapter.h new file mode 100644 index 0000000..2787176 --- /dev/null +++ b/src/socket/Adapter.h @@ -0,0 +1,49 @@ +#pragma once + +#include "../system/System.h" + +#include +#include +#include + +#include + +namespace Socket +{ + class Adapter + { + public: + virtual ~Adapter() noexcept = default; + + virtual System::native_socket_type get_handle() const noexcept = 0; + virtual ::gnutls_session_t get_tls_session() const noexcept = 0; + virtual Adapter *copy() const noexcept = 0; + + long nonblock_recv( + std::vector &buf, + const std::chrono::milliseconds &timeout + ) const noexcept; + + virtual long nonblock_recv( + void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept = 0; + + long nonblock_send( + const std::string &buf, + const std::chrono::milliseconds &timeout + ) const noexcept; + + virtual long nonblock_send( + const void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept = 0; + + virtual void close() noexcept = 0; + + bool operator ==(const Adapter &obj) const noexcept; + bool operator !=(const Adapter &obj) const noexcept; + }; +} diff --git a/src/socket/AdapterDefault.cpp b/src/socket/AdapterDefault.cpp new file mode 100644 index 0000000..2940daf --- /dev/null +++ b/src/socket/AdapterDefault.cpp @@ -0,0 +1,43 @@ + +#include "AdapterDefault.h" + +namespace Socket +{ + AdapterDefault::AdapterDefault(const Socket &sock) noexcept : sock(sock) {} + + System::native_socket_type AdapterDefault::get_handle() const noexcept { + return sock.get_handle(); + } + + ::gnutls_session_t AdapterDefault::get_tls_session() const noexcept { + return nullptr; + } + + Adapter *AdapterDefault::copy() const noexcept { + return new AdapterDefault(this->sock); + } + + long AdapterDefault::nonblock_recv( + void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept { + return sock.nonblock_recv(buf, length, timeout); + } + + long AdapterDefault::nonblock_send( + const void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept { + return sock.nonblock_send(buf, length, timeout); + } + + void AdapterDefault::close() noexcept { + // Wait for send all data to client + sock.nonblock_send_sync(); + + sock.shutdown(); + sock.close(); + } +} diff --git a/src/socket/AdapterDefault.h b/src/socket/AdapterDefault.h new file mode 100644 index 0000000..db8b191 --- /dev/null +++ b/src/socket/AdapterDefault.h @@ -0,0 +1,35 @@ +#pragma once + +#include "Adapter.h" +#include "Socket.h" + +namespace Socket +{ + class AdapterDefault : public Adapter + { + private: + Socket sock; + + public: + AdapterDefault() = delete; + AdapterDefault(const Socket &_sock) noexcept; + + virtual System::native_socket_type get_handle() const noexcept override; + virtual ::gnutls_session_t get_tls_session() const noexcept override; + virtual Adapter *copy() const noexcept override; + + virtual long nonblock_recv( + void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept override; + + virtual long nonblock_send( + const void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept override; + + virtual void close() noexcept override; + }; +} diff --git a/src/socket/AdapterTls.cpp b/src/socket/AdapterTls.cpp new file mode 100644 index 0000000..0126830 --- /dev/null +++ b/src/socket/AdapterTls.cpp @@ -0,0 +1,163 @@ + +#include "AdapterTls.h" + +namespace Socket +{ + AdapterTls::AdapterTls( + const Socket &sock, + ::gnutls_priority_t priority_cache, + ::gnutls_certificate_credentials_t x509_cred + ) noexcept { + // https://leandromoreira.com.br/2015/10/12/how-to-optimize-nginx-configuration-for-http2-tls-ssl/ + // https://www.gnutls.org/manual/html_node/False-Start.html + + #ifdef GNUTLS_ENABLE_FALSE_START + constexpr int flags = GNUTLS_SERVER | GNUTLS_ENABLE_FALSE_START; + #else + constexpr int flags = GNUTLS_SERVER; + #endif + + ::gnutls_init(&this->session, flags); + ::gnutls_priority_set(this->session, priority_cache); + ::gnutls_credentials_set(this->session, GNUTLS_CRD_CERTIFICATE, x509_cred); + + ::gnutls_certificate_server_set_request(this->session, GNUTLS_CERT_IGNORE); + + ::gnutls_transport_set_int2(this->session, sock.get_handle(), sock.get_handle() ); + + char h2[] = "h2"; + char http11[] = "http/1.1"; + + const ::gnutls_datum_t protocols[] { + { reinterpret_cast(h2), sizeof(h2) - 1 }, + { reinterpret_cast(http11), sizeof(http11) - 1 }, + }; + + ::gnutls_alpn_set_protocols(this->session, protocols, sizeof(protocols) / sizeof(::gnutls_datum_t), 0); + } + + AdapterTls::AdapterTls(const ::gnutls_session_t session) noexcept : session(session) {} + + bool AdapterTls::handshake() noexcept + { + int ret; + + ::gnutls_handshake_set_timeout(this->session, GNUTLS_DEFAULT_HANDSHAKE_TIMEOUT); + + do { + ret = ::gnutls_handshake(this->session); + } + while (ret < 0 && ::gnutls_error_is_fatal(ret) == 0); + + if (ret < 0) { + Socket sock(this->get_handle() ); + + sock.close(); + ::gnutls_deinit(this->session); + + return false; + } + + return true; + } + + long AdapterTls::nonblock_send_all( + const void *buf, const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept { + size_t record_size = length; + + if (0 == record_size) { + return -1; + } + + Socket sock(this->get_handle() ); + + size_t total = 0; + + while (total < length) { + if (record_size > length - total) { + record_size = length - total; + } + + long send_size = 0; + + do { + sock.nonblock_send_sync(); + + send_size = ::gnutls_record_send( + this->session, + reinterpret_cast(buf) + total, + record_size + ); + } + while (GNUTLS_E_AGAIN == send_size); + + if (send_size < 0) { + return send_size; + } + + total += long(send_size); + } + + return long(total); + } + + System::native_socket_type AdapterTls::get_handle() const noexcept { + return static_cast( + ::gnutls_transport_get_int(this->session) + ); + } + + ::gnutls_session_t AdapterTls::get_tls_session() const noexcept { + return this->session; + } + + Adapter *AdapterTls::copy() const noexcept { + return new AdapterTls(this->session); + } + + long AdapterTls::nonblock_recv( + void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept { + Socket sock(this->get_handle() ); + + long result; + + do { + if (sock.nonblock_recv_sync(timeout) == false) { + // Timeout + result = -1; + break; + } + + result = ::gnutls_record_recv(this->session, buf, length); + } + while (GNUTLS_E_AGAIN == result || GNUTLS_E_INTERRUPTED == result); + + return result; + } + + long AdapterTls::nonblock_send( + const void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept { + return this->nonblock_send_all(buf, length, timeout); + } + + void AdapterTls::close() noexcept { + Socket sock(this->get_handle() ); + + // Wait for send all data to client + sock.nonblock_send_sync(); + + ::gnutls_bye(this->session, GNUTLS_SHUT_RDWR); + + sock.close(); + + ::gnutls_deinit(this->session); + } +} diff --git a/src/socket/AdapterTls.h b/src/socket/AdapterTls.h new file mode 100644 index 0000000..04aab40 --- /dev/null +++ b/src/socket/AdapterTls.h @@ -0,0 +1,50 @@ +#pragma once + +#include "Adapter.h" +#include "Socket.h" + +namespace Socket +{ + class AdapterTls : public Adapter + { + private: + ::gnutls_session_t session; + + public: + AdapterTls() = delete; + + AdapterTls( + const Socket &sock, + ::gnutls_priority_t priority_cache, + ::gnutls_certificate_credentials_t x509_cred + ) noexcept; + + AdapterTls(const ::gnutls_session_t session) noexcept; + + bool handshake() noexcept; + + long nonblock_send_all( + const void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept; + + virtual System::native_socket_type get_handle() const noexcept override; + virtual ::gnutls_session_t get_tls_session() const noexcept override; + virtual Adapter *copy() const noexcept override; + + virtual long nonblock_recv( + void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept override; + + virtual long nonblock_send( + const void *buf, + const size_t length, + const std::chrono::milliseconds &timeout + ) const noexcept override; + + virtual void close() noexcept override; + }; +} diff --git a/src/socket/List.cpp b/src/socket/List.cpp new file mode 100644 index 0000000..eab986d --- /dev/null +++ b/src/socket/List.cpp @@ -0,0 +1,408 @@ + +#include "List.h" + +#ifdef POSIX + #include + #include + #include +#endif + +namespace Socket +{ + List::List() noexcept + { + #ifdef WIN32 + this->obj_list = INVALID_HANDLE_VALUE; + #elif POSIX + this->obj_list = ~0; + #else + #error "Undefined platform" + #endif + } + + List::List(List &&obj) noexcept + : obj_list(obj.obj_list) + { + #ifdef WIN32 + obj.obj_list = INVALID_HANDLE_VALUE; + obj.poll_events.swap(this->poll_events); + #elif POSIX + obj.obj_list = ~0; + obj.epoll_events.swap(this->epoll_events); + #else + #error "Undefined platform" + #endif + } + + List::~List() noexcept { + this->destroy(); + } + + bool List::create(const size_t startListSize) + { + this->destroy(); + + #ifdef WIN32 + this->obj_list = (HANDLE) 1; + + if (startListSize > 0) { + this->poll_events.reserve(startListSize); + } + + return true; + #elif POSIX + this->obj_list = ::epoll_create(startListSize); + + if (this->obj_list == ~0) { + return false; + } + + if (startListSize > 0) { + this->epoll_events.reserve(startListSize); + } + + return true; + #else + #error "Undefined platform" + #endif + } + + void List::destroy() noexcept + { + if (this->is_created() ) + { + #ifdef WIN32 + this->obj_list = INVALID_HANDLE_VALUE; + this->poll_events.clear(); + #elif POSIX + ::close(this->obj_list); + this->obj_list = ~0; + this->epoll_events.clear(); + #else + #error "Undefined platform" + #endif + } + } + + bool List::is_created() const noexcept + { + #ifdef WIN32 + return this->obj_list != INVALID_HANDLE_VALUE; + #elif POSIX + return this->obj_list != ~0; + #else + #error "Undefined platform" + #endif + } + + bool List::addSocket(const Socket &sock) noexcept + { + if (this->is_created() == false) { + return false; + } + + #ifdef WIN32 + WSAPOLLFD event { + sock.get_handle(), + POLLRDNORM, + 0 + }; + + poll_events.emplace_back(event); + + return true; + #elif POSIX + struct ::epoll_event event { + EPOLLIN | EPOLLET | EPOLLRDHUP, + reinterpret_cast(sock.get_handle() ) + }; + + const int result = ::epoll_ctl( + this->obj_list, + EPOLL_CTL_ADD, + sock.get_handle(), + &event + ); + + if (result == ~0) { + return false; + } + + this->epoll_events.emplace_back(); + + return true; + #else + #error "Undefined platform" + #endif + } + + bool List::removeSocket(const Socket &sock) noexcept + { + if (this->is_created() == false) { + return false; + } + + #ifdef WIN32 + for (size_t i = 0; i < poll_events.size(); ++i) { + if (sock.get_handle() == poll_events[i].fd) { + poll_events.erase(poll_events.begin() + i); + return true; + } + } + + return false; + #elif POSIX + const int result = ::epoll_ctl( + this->obj_list, + EPOLL_CTL_DEL, + sock.get_handle(), + nullptr + ); + + if (result == ~0) { + return false; + } + + this->epoll_events.pop_back(); + + return true; + #else + #error "Undefined platform" + #endif + } + + bool List::accept(std::vector &sockets) const noexcept + { + if (this->is_created() ) + { + #ifdef WIN32 + const int count = ::WSAPoll( + this->poll_events.data(), + static_cast<::ULONG>(this->poll_events.size() ), + ~0 + ); + + if (SOCKET_ERROR == count) { + return false; + } + + for (size_t i = 0; i < this->poll_events.size(); ++i) + { + const WSAPOLLFD &event = this->poll_events[i]; + + if (event.revents & POLLRDNORM) + { + System::native_socket_type client_socket = ~0; + + do { + client_socket = ::accept( + event.fd, + static_cast(nullptr), + static_cast(nullptr) + ); + + if (~0 != client_socket) { + sockets.emplace_back(Socket(client_socket) ); + } + } + while (~0 != client_socket); + } + } + + return false == sockets.empty(); + #elif POSIX + const int count = ::epoll_wait( + this->obj_list, + this->epoll_events.data(), + this->epoll_events.size(), + ~0 + ); + + if (count == ~0) { + return false; + } + + for (int i = 0; i < count; ++i) + { + const epoll_event &event = this->epoll_events[i]; + + if (event.events & EPOLLIN) + { + System::native_socket_type client_socket = ~0; + + do { + client_socket = ::accept( + event.data.fd, + static_cast(nullptr), + static_cast(nullptr) + ); + + if (~0 != client_socket) { + sockets.emplace_back(Socket(client_socket) ); + } + } + while (~0 != client_socket); + } + } + + return false == sockets.empty(); + #else + #error "Undefined platform" + #endif + } + + return false; + } + + bool List::accept( + std::vector &sockets, + std::vector &socketsAddress + ) const noexcept { + if (this->is_created() ) + { + #ifdef WIN32 + const int count = ::WSAPoll( + this->poll_events.data(), + static_cast<::ULONG>(this->poll_events.size() ), + ~0 + ); + + if (SOCKET_ERROR == count) { + return false; + } + + for (auto const &event : this->poll_events) + { + if (event.revents & POLLRDNORM) + { + System::native_socket_type client_socket = ~0; + + do { + ::sockaddr_in client_addr {}; + ::socklen_t client_addr_len = sizeof(client_addr); + + client_socket = ::accept( + event.fd, + reinterpret_cast<::sockaddr *>(&client_addr), + &client_addr_len + ); + + if (~0 != client_socket) { + sockets.emplace_back(Socket(client_socket) ); + socketsAddress.emplace_back(client_addr); + } + } + while (~0 != client_socket); + } + } + + return false == sockets.empty(); + #elif POSIX + const int count = ::epoll_wait( + this->obj_list, + this->epoll_events.data(), + this->epoll_events.size(), + ~0 + ); + + if (count == ~0) { + return false; + } + + for (int i = 0; i < count; ++i) + { + const epoll_event &event = this->epoll_events[i]; + + if (event.events & EPOLLIN) + { + System::native_socket_type client_socket = ~0; + + do { + ::sockaddr_in client_addr {}; + socklen_t client_addr_len = sizeof(client_addr); + + client_socket = ::accept( + event.data.fd, + reinterpret_cast<::sockaddr *>(&client_addr), + &client_addr_len + ); + + if (~0 != client_socket) { + sockets.emplace_back(Socket(client_socket) ); + socketsAddress.emplace_back(client_addr); + } + } + while (~0 != client_socket); + } + } + + return false == sockets.empty(); + #else + #error "Undefined platform" + #endif + } + + return false; + } + + bool List::recv( + std::vector &sockets, + std::vector &disconnected, + std::chrono::milliseconds timeout + ) const noexcept { + if (this->is_created() == false) { + return false; + } + + #ifdef WIN32 + const int count = ::WSAPoll( + this->poll_events.data(), + static_cast<::ULONG>(this->poll_events.size() ), + static_cast<::INT>(timeout.count() ) + ); + + if (SOCKET_ERROR == count) { + return false; + } + + for (auto const &event : this->poll_events) + { + if (event.revents & POLLRDNORM) { + sockets.emplace_back(Socket(event.fd) ); + } + else if (event.revents & POLLHUP) { + disconnected.emplace_back(Socket(event.fd) ); + } + } + + return false == sockets.empty() || false == disconnected.empty(); + #elif POSIX + const int count = ::epoll_wait( + this->obj_list, + this->epoll_events.data(), + this->epoll_events.size(), + timeout.count() + ); + + if (count == ~0) { + return false; + } + + for (int i = 0; i < count; ++i) + { + const epoll_event &event = this->epoll_events[i]; + + if (event.events & EPOLLIN) { + sockets.emplace_back(Socket(event.data.fd) ); + } + else if (event.events & EPOLLRDHUP) { + disconnected.emplace_back(Socket(event.data.fd) ); + } + } + + return false == sockets.empty() || false == disconnected.empty(); + #else + #error "Undefined platform" + #endif + } +} diff --git a/src/socket/List.h b/src/socket/List.h new file mode 100644 index 0000000..cdd2747 --- /dev/null +++ b/src/socket/List.h @@ -0,0 +1,53 @@ +#pragma once + +#include "Socket.h" + +#ifdef POSIX + #include + #include +#endif + +namespace Socket +{ + class List + { + protected: + #ifdef WIN32 + HANDLE obj_list; + mutable std::vector poll_events; + #elif POSIX + int obj_list; + mutable std::vector epoll_events; + #else + #error "Undefined platform" + #endif + + public: + List() noexcept; + List(List &&obj) noexcept; + ~List() noexcept; + + bool create(const size_t startListSize = 1); + void destroy() noexcept; + + bool is_created() const noexcept; + + bool addSocket(const Socket &sock) noexcept; + bool removeSocket(const Socket &sock) noexcept; + + bool accept( + std::vector &sockets + ) const noexcept; + + bool accept( + std::vector &sockets, + std::vector &socketsAddress + ) const noexcept; + + bool recv( + std::vector &sockets, + std::vector &errors, + std::chrono::milliseconds timeout = std::chrono::milliseconds(~0) + ) const noexcept; + }; +} diff --git a/src/socket/Socket.cpp b/src/socket/Socket.cpp new file mode 100644 index 0000000..40b0f92 --- /dev/null +++ b/src/socket/Socket.cpp @@ -0,0 +1,552 @@ + +#include "Socket.h" + +#ifdef POSIX + #include + #include + #include + #include + #include + #include + #include +#endif + +namespace Socket +{ + bool Socket::Startup() noexcept + { + #ifdef WIN32 + unsigned short version = MAKEWORD(2, 2); + ::WSADATA wsaData = {0}; + return 0 == ::WSAStartup(version, &wsaData); + #elif POSIX + return true; + #else + #error "Undefined platform" + #endif + } + + bool Socket::Cleanup() noexcept + { + #ifdef WIN32 + return 0 == ::WSACleanup(); + #elif POSIX + return true; + #else + #error "Undefined platform" + #endif + } + + int Socket::getLastError() noexcept + { + #ifdef WIN32 + return ::WSAGetLastError(); + #elif POSIX + return errno; + #else + #error "Undefined platform" + #endif + } + + Socket::Socket() noexcept : socket_handle(~0) {} + + Socket::Socket(const System::native_socket_type fd) noexcept : socket_handle(fd) {} + + Socket::Socket(const Socket &obj) noexcept : socket_handle(obj.socket_handle) {} + + Socket::Socket(Socket &&obj) noexcept + : socket_handle(obj.socket_handle) + { + obj.socket_handle = ~0; + } + + bool Socket::open() noexcept { + this->close(); + this->socket_handle = ::socket(AF_INET, SOCK_STREAM, 0); + return this->is_open(); + } + + bool Socket::close() noexcept + { + if (this->is_open() ) + { + #ifdef WIN32 + const int result = ::closesocket(this->socket_handle); + #elif POSIX + const int result = ::close(this->socket_handle); + #else + #error "Undefined platform" + #endif + + if (0 == result) { + this->socket_handle = ~0; + return true; + } + } + + return false; + } + + bool Socket::is_open() const noexcept + { + #ifdef WIN32 + return INVALID_SOCKET != this->socket_handle; + #elif POSIX + return ~0 != this->socket_handle; + #else + #error "Undefined platform" + #endif + } + + System::native_socket_type Socket::get_handle() const noexcept { + return this->socket_handle; + } + + bool Socket::bind(const int port) const noexcept { + const ::sockaddr_in sock_addr { + AF_INET, + ::htons(port), + ::htonl(INADDR_ANY), + 0 + }; + + return 0 == ::bind( + this->socket_handle, + reinterpret_cast(&sock_addr), + sizeof(sockaddr_in) + ); + } + + bool Socket::listen() const noexcept { + return 0 == ::listen(this->socket_handle, SOMAXCONN); + } + + Socket Socket::accept() const noexcept + { + #ifdef WIN32 + System::native_socket_type client_socket = ::accept( + this->socket_handle, + static_cast(nullptr), + static_cast(nullptr) + ); + #elif POSIX + System::native_socket_type client_socket = ::accept( + this->socket_handle, + static_cast(nullptr), + static_cast(nullptr) + ); + #else + #error "Undefined platform" + #endif + return Socket(client_socket); + } + + Socket Socket::nonblock_accept() const noexcept + { + System::native_socket_type client_socket = ~0; + #ifdef WIN32 + WSAPOLLFD event { + this->socket_handle, + POLLRDNORM, + 0 + }; + + if (1 == ::WSAPoll(&event, 1, ~0) && event.revents & POLLRDNORM) { + client_socket = ::accept( + this->socket_handle, + static_cast(nullptr), + static_cast(nullptr) + ); + } + #elif POSIX + struct ::pollfd event { + this->socket_handle, + POLLIN, + 0 + }; + + if (1 == ::poll(&event, 1, ~0) && event.revents & POLLIN) { + client_socket = ::accept( + this->socket_handle, + static_cast(nullptr), + static_cast(nullptr) + ); + } + #else + #error "Undefined platform" + #endif + return Socket(client_socket); + } + + Socket Socket::nonblock_accept(const std::chrono::milliseconds &timeout) const noexcept + { + System::native_socket_type client_socket = ~0; + #ifdef WIN32 + WSAPOLLFD event { + this->socket_handle, + POLLRDNORM, + 0 + }; + + if (1 == ::WSAPoll(&event, 1, int(timeout.count()) ) && event.revents & POLLRDNORM) { + client_socket = ::accept( + this->socket_handle, + static_cast(nullptr), + static_cast