├── .gitignore ├── Block.hpp ├── BlockChain.hpp ├── Makefile ├── README.md ├── client_http.hpp ├── common.hpp ├── crypto.hpp ├── hash.hpp ├── js_client_test.html ├── json.hh ├── license.txt ├── main.cpp ├── requests.hpp ├── server_http.hpp ├── status_code.hpp ├── test.sh └── utility.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | main 3 | *.out 4 | *.o 5 | .DS_Store 6 | **/.DS_Store -------------------------------------------------------------------------------- /Block.hpp: -------------------------------------------------------------------------------- 1 | //author: tko 2 | #ifndef BLOCK_H 3 | #define BLOCK_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "json.hh" 12 | using json = nlohmann::json; 13 | 14 | class Block { 15 | public: 16 | Block(int index, string prevHas, string hash, string nonce, vector data); 17 | string getPreviousHash(void); 18 | string getHash(void); 19 | int getIndex(void); 20 | vector getData(void); 21 | 22 | void toString(void); 23 | json toJSON(void); 24 | private: 25 | int index; 26 | string previousHash; 27 | string blockHash; 28 | string nonce; 29 | vector data; 30 | // string getMerkleRoot(const vector &merkle); 31 | }; 32 | // Constructor 33 | Block::Block(int index, string prevHash, string hash, string nonce, vector data ) { 34 | printf("\nInitializing Block: %d ---- Hash: %s \n", index,hash.c_str()); 35 | this -> previousHash = prevHash; 36 | this -> data = data; 37 | this -> index = index; 38 | this -> nonce = nonce; 39 | this -> blockHash = hash; 40 | 41 | } 42 | 43 | int Block::getIndex(void) { 44 | return this -> index; 45 | } 46 | 47 | string Block::getPreviousHash(void) { 48 | return this -> previousHash; 49 | } 50 | 51 | string Block::getHash(void) { 52 | return this -> blockHash; 53 | } 54 | 55 | vector Block::getData(void){ 56 | return this -> data; 57 | } 58 | 59 | // Prints Block data 60 | void Block::toString(void) { 61 | string dataString; 62 | for (int i=0; i < data.size(); i++) 63 | dataString += data[i] + ", "; 64 | printf("\n-------------------------------\n"); 65 | printf("Block %d\nHash: %s\nPrevious Hash: %s\nContents: %s", 66 | index,this->blockHash.c_str(),this->previousHash.c_str(),dataString.c_str()); 67 | printf("\n-------------------------------\n"); 68 | } 69 | 70 | json Block::toJSON(void) { 71 | json j; 72 | j["index"] = this->index; 73 | j["hash"] = this->blockHash; 74 | j["previousHash"] = this->previousHash; 75 | j["nonce"] = this->nonce; 76 | j["data"] = this->data; 77 | return j; 78 | } 79 | 80 | #endif -------------------------------------------------------------------------------- /BlockChain.hpp: -------------------------------------------------------------------------------- 1 | //author: tko 2 | #ifndef BLOCKCHAIN_H 3 | #define BLOCKCHAIN_H 4 | 5 | #include 6 | #include 7 | #include "hash.hpp" 8 | #include 9 | #include 10 | #include 11 | 12 | #include "json.hh" 13 | using json = nlohmann::json; 14 | 15 | class BlockChain { 16 | public: 17 | BlockChain(int genesis = 1 ); 18 | Block getBlock(int index); 19 | // getBlock(string hash); //not implemented 20 | int getNumOfBlocks(void); 21 | int addBlock(int index, string prevHash, string hash, string nonce, vector &merkle); 22 | string getLatestBlockHash(void); 23 | // void toString(void); 24 | string toJSON(void); 25 | int replaceChain(json chain); 26 | private: 27 | vector > blockchain; //vector that is the blockchain 28 | }; 29 | 30 | // If integer passed into constructor is 0, it the first node and creates the genesis block 31 | BlockChain::BlockChain(int genesis ){ 32 | if (genesis == 0) { 33 | vector v; 34 | v.push_back("Genesis Block!"); 35 | // string header = to_string(0) + string("00000000000000") + getMerkleRoot(v); 36 | auto hash_nonce_pair = findHash(0,string("00000000000000"),v); 37 | 38 | this -> blockchain.push_back(std::make_unique(0,string("00000000000000"),hash_nonce_pair.first,hash_nonce_pair.second,v)); 39 | printf("Created blockchain!\n"); 40 | } 41 | } 42 | // Gets block based on the index 43 | Block BlockChain::getBlock(int index) { 44 | for ( int i = 0; i getIndex() == index) { 46 | return *(blockchain[i]); 47 | } 48 | } 49 | throw invalid_argument("Index does not exist."); 50 | } 51 | 52 | // returns number of blocks 53 | int BlockChain::getNumOfBlocks(void) { 54 | return this -> blockchain.size(); 55 | } 56 | 57 | // checks whether data fits with the right hash -> add block 58 | int BlockChain::addBlock(int index, string prevHash, string hash, string nonce, vector &merkle) { 59 | string header = to_string(index) + prevHash + getMerkleRoot(merkle) + nonce; 60 | if ( (!sha256(header).compare(hash)) && (hash.substr(0,2) == "00" ) && (index == blockchain.size())) { 61 | printf("Block hashes match --- Adding Block %s \n",hash.c_str()); 62 | this->blockchain.push_back(std::make_unique(index,prevHash,hash,nonce,merkle)); 63 | return 1; 64 | } 65 | cout << "Hash doesn't match criteria\n"; 66 | return 0; 67 | } 68 | 69 | // returns hash of the latest block, used for finding the previousHash when mining new block 70 | string BlockChain::getLatestBlockHash(void) { 71 | return this->blockchain[blockchain.size()-1]->getHash(); 72 | } 73 | 74 | // returns JSON string of JSON - used to send to network 75 | string BlockChain::toJSON() { 76 | json j; 77 | j["length"] = this->blockchain.size(); 78 | for (int i = 0; i < this->blockchain.size(); i++){ 79 | j["data"][this->blockchain[i]->getIndex()] = this->blockchain[i]->toJSON(); 80 | } 81 | return j.dump(3); 82 | } 83 | 84 | // replaces Chain with new chain represented by a JSON, used when node sends new blockchain 85 | int BlockChain::replaceChain(json chain) { 86 | //remove all blocks except for the first block 87 | while (this->blockchain.size() > 1){ 88 | this->blockchain.pop_back(); 89 | } 90 | for (int a = 1; a (); a++ ){ 91 | auto block = chain["data"][a]; 92 | vector data = block["data"].get >(); 93 | this->addBlock(block["index"],block["previousHash"],block["hash"],block["nonce"],data); 94 | } 95 | return 1; 96 | } 97 | 98 | #endif -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | EXENAME = main 2 | OBJS = main.o 3 | 4 | CXX = g++ 5 | CXXFLAGS = -std=c++14 -stdlib=libc++ -lssl -lcrypto -Wall -lboost_system 6 | 7 | main.o: main.cpp 8 | $(CXX) $(CXXFLAGS) main.cpp -o $(OBJS) 9 | 10 | clean: 11 | -rm -f *.o $(EXENAME) 12 | 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-blockchain 2 | 3 | My implementation of a blockchain in C++ I created for fun :) 4 | 5 | _Note that this was written before knowing more about distributed system concepts. Use at your own discretion._ 6 | 7 | Follows some Bitcoin design principles including a peer-to-peer network, SHA-256 to hash headers and blocks, merkle trees, and "mining"(more on that below). 8 | 9 | #### Requirements 10 | 11 | - Uses C++14, OpenSSL library, Simple-Web-Server, and a [JSON library](https://github.com/nlohmann/json) 12 | 13 | Includes a Command line interface that allows you to view blockchains at different indices and add new blocks. You 14 | can do that 20 times until it automatically quits but you can change that. Control-c to quit. 15 | 16 | And unfortunately, everything is stored in memory and is deleted when program quits. 17 | 18 | ## Peer-to-Peer Network 19 | 20 | At first, I used WebSockets but a peer-to-peer system would require setting up a WS server and WS clients for each and every node. 21 | 22 | So instead, I make HTTP requests to connect to nodes to the network 23 | 24 | #### For this to work we need to: 25 | 26 | - Keep track of nodes in the network 27 | - Get the latest chains from every node -- to validate your chain & get up to date when a new node is added to the network 28 | - Send out your chain to the network when a new block is added 29 | 30 | #### Conflicts in different chains 31 | 32 | There can only be one explicit set of blocks in the chain at a given time. If there are conflicts(e.g. when the chains at different nodes have the same size but have different blocks), the longest chain is chosen. 33 | 34 | So if some other node sends in a new chain that's longer than yours, your chain is replaced. 35 | 36 | **Note:** this was a simple implementation and thus, it replaces the entire chain except for the genesis block. For future improvements, each node should check the new chain with other nodes before it is added and entire nodes shouldn't be sent out. I didn't have access to other computers in the same network, so the nodes are connected through different ports inside your computer. 37 | 38 | ## BlockChain Object 39 | 40 | #### Private Variables: 41 | 42 | - blockchain(vector >): vector of smart pointers to Block objects 43 | 44 | Genesis Block is created during intialization. 45 | 46 | #### Validating the integrity of blocks 47 | 48 | Every time you want to add a block to the blockchain, you will need to provide it: 49 | 50 | `int index, string prevHash, string hash, string nonce, vector &merkle` 51 | 52 | - index: index of the block 53 | - prevHash: hash of the previous block 54 | - nonce: self-explantory 55 | - merkle: vector holding in the data of the block 56 | 57 | It will then check whether you have the correct hash(it rehashes the information given), if you have "00" in front, and whether your index is correct. 58 | 59 | Note: this is the only way to add to the blockchain. 60 | 61 | ## Mining for I guess... Proof of Stake 62 | 63 | (I made it very simple because I didn't want to spend much processing power so it's honestly not proof of stake but I just added it in for fun) - use findHash() to get hash and nonce 64 | 65 | first two characters of the hash must be 0 66 | 67 | - e.g. `003d9dc40cad6b414d45555e4b83045cfde74bcee6b09fb42536ca2500087fd9` works 68 | 69 | ## Block Object 70 | 71 | Hash header: index + prevHash + merkleRoot(data) + nonce 72 | 73 | #### Private Variables: 74 | 75 | - index 76 | - Data: vector of strings 77 | - previousHash 78 | - blockHash 79 | - nonce 80 | 81 | For a block to be immutable, its properties are private and there are only methods that return them but not update them. 82 | 83 | ## Common Functions 84 | 85 | #### getMerkleRoot(const vector &merkle) 86 | 87 | - gets merkle root based on elements of a vector 88 | 89 | #### findHash(int index, string prevHash, vector &merkle) 90 | 91 | - "Mining" part 92 | - finds hash and returns a std::pair of the hash found and nonce used to find it 93 | 94 | ### Author 95 | 96 | tk2@illinois.edu 97 | -------------------------------------------------------------------------------- /client_http.hpp: -------------------------------------------------------------------------------- 1 | #ifndef CLIENT_HTTP_HPP 2 | #define CLIENT_HTTP_HPP 3 | 4 | #include "utility.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #ifdef USE_STANDALONE_ASIO 12 | #include 13 | #include 14 | namespace SimpleWeb { 15 | using error_code = std::error_code; 16 | using errc = std::errc; 17 | using system_error = std::system_error; 18 | namespace make_error_code = std; 19 | using string_view = const std::string &; // TODO c++17: use std::string_view 20 | } // namespace SimpleWeb 21 | #else 22 | #include 23 | #include 24 | #include 25 | namespace SimpleWeb { 26 | namespace asio = boost::asio; 27 | using error_code = boost::system::error_code; 28 | namespace errc = boost::system::errc; 29 | using system_error = boost::system::system_error; 30 | namespace make_error_code = boost::system::errc; 31 | using string_view = boost::string_ref; 32 | } // namespace SimpleWeb 33 | #endif 34 | 35 | namespace SimpleWeb { 36 | template 37 | class Client; 38 | 39 | template 40 | class ClientBase { 41 | public: 42 | class Content : public std::istream { 43 | friend class ClientBase; 44 | 45 | public: 46 | std::size_t size() noexcept { 47 | return streambuf.size(); 48 | } 49 | /// Convenience function to return std::string. The stream buffer is consumed. 50 | std::string string() noexcept { 51 | try { 52 | std::stringstream ss; 53 | ss << rdbuf(); 54 | return ss.str(); 55 | } 56 | catch(...) { 57 | return std::string(); 58 | } 59 | } 60 | 61 | private: 62 | asio::streambuf &streambuf; 63 | Content(asio::streambuf &streambuf) noexcept : std::istream(&streambuf), streambuf(streambuf) {} 64 | }; 65 | 66 | class Response { 67 | friend class ClientBase; 68 | friend class Client; 69 | 70 | asio::streambuf streambuf; 71 | 72 | Response(std::size_t max_response_streambuf_size) noexcept : streambuf(max_response_streambuf_size), content(streambuf) {} 73 | 74 | public: 75 | std::string http_version, status_code; 76 | 77 | Content content; 78 | 79 | CaseInsensitiveMultimap header; 80 | }; 81 | 82 | class Config { 83 | friend class ClientBase; 84 | 85 | private: 86 | Config() noexcept {} 87 | 88 | public: 89 | /// Set timeout on requests in seconds. Default value: 0 (no timeout). 90 | long timeout = 0; 91 | /// Set connect timeout in seconds. Default value: 0 (Config::timeout is then used instead). 92 | long timeout_connect = 0; 93 | /// Maximum size of response stream buffer. Defaults to architecture maximum. 94 | /// Reaching this limit will result in a message_size error code. 95 | std::size_t max_response_streambuf_size = std::numeric_limits::max(); 96 | /// Set proxy server (server:port) 97 | std::string proxy_server; 98 | }; 99 | 100 | protected: 101 | class Connection : public std::enable_shared_from_this { 102 | public: 103 | template 104 | Connection(std::shared_ptr handler_runner, long timeout, Args &&... args) noexcept 105 | : handler_runner(std::move(handler_runner)), timeout(timeout), socket(new socket_type(std::forward(args)...)) {} 106 | 107 | std::shared_ptr handler_runner; 108 | long timeout; 109 | 110 | std::unique_ptr socket; // Socket must be unique_ptr since asio::ssl::stream is not movable 111 | bool in_use = false; 112 | bool attempt_reconnect = true; 113 | 114 | std::unique_ptr timer; 115 | 116 | void set_timeout(long seconds = 0) noexcept { 117 | if(seconds == 0) 118 | seconds = timeout; 119 | if(seconds == 0) { 120 | timer = nullptr; 121 | return; 122 | } 123 | timer = std::unique_ptr(new asio::steady_timer(socket->get_io_service())); 124 | timer->expires_from_now(std::chrono::seconds(seconds)); 125 | auto self = this->shared_from_this(); 126 | timer->async_wait([self](const error_code &ec) { 127 | if(!ec) { 128 | error_code ec; 129 | self->socket->lowest_layer().cancel(ec); 130 | } 131 | }); 132 | } 133 | 134 | void cancel_timeout() noexcept { 135 | if(timer) { 136 | error_code ec; 137 | timer->cancel(ec); 138 | } 139 | } 140 | }; 141 | 142 | class Session { 143 | public: 144 | Session(std::size_t max_response_streambuf_size, std::shared_ptr connection, std::unique_ptr request_streambuf) noexcept 145 | : connection(std::move(connection)), request_streambuf(std::move(request_streambuf)), response(new Response(max_response_streambuf_size)) {} 146 | 147 | std::shared_ptr connection; 148 | std::unique_ptr request_streambuf; 149 | std::shared_ptr response; 150 | std::function &, const error_code &)> callback; 151 | }; 152 | 153 | public: 154 | /// Set before calling request 155 | Config config; 156 | 157 | /// If you have your own asio::io_service, store its pointer here before calling request(). 158 | /// When using asynchronous requests, running the io_service is up to the programmer. 159 | std::shared_ptr io_service; 160 | 161 | /// Convenience function to perform synchronous request. The io_service is run within this function. 162 | /// If reusing the io_service for other tasks, use the asynchronous request functions instead. 163 | /// Do not use concurrently with the asynchronous request functions. 164 | std::shared_ptr request(const std::string &method, const std::string &path = std::string("/"), 165 | string_view content = "", const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 166 | std::shared_ptr response; 167 | error_code ec; 168 | request(method, path, content, header, [&response, &ec](std::shared_ptr response_, const error_code &ec_) { 169 | response = response_; 170 | ec = ec_; 171 | }); 172 | 173 | { 174 | std::unique_lock lock(concurrent_synchronous_requests_mutex); 175 | ++concurrent_synchronous_requests; 176 | } 177 | io_service->run(); 178 | { 179 | std::unique_lock lock(concurrent_synchronous_requests_mutex); 180 | --concurrent_synchronous_requests; 181 | if(!concurrent_synchronous_requests) 182 | io_service->reset(); 183 | } 184 | 185 | if(ec) 186 | throw system_error(ec); 187 | 188 | return response; 189 | } 190 | 191 | /// Convenience function to perform synchronous request. The io_service is run within this function. 192 | /// If reusing the io_service for other tasks, use the asynchronous request functions instead. 193 | /// Do not use concurrently with the asynchronous request functions. 194 | std::shared_ptr request(const std::string &method, const std::string &path, std::istream &content, 195 | const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 196 | std::shared_ptr response; 197 | error_code ec; 198 | request(method, path, content, header, [&response, &ec](std::shared_ptr response_, const error_code &ec_) { 199 | response = response_; 200 | ec = ec_; 201 | }); 202 | 203 | { 204 | std::unique_lock lock(concurrent_synchronous_requests_mutex); 205 | ++concurrent_synchronous_requests; 206 | } 207 | io_service->run(); 208 | { 209 | std::unique_lock lock(concurrent_synchronous_requests_mutex); 210 | --concurrent_synchronous_requests; 211 | if(!concurrent_synchronous_requests) 212 | io_service->reset(); 213 | } 214 | 215 | if(ec) 216 | throw system_error(ec); 217 | 218 | return response; 219 | } 220 | 221 | /// Asynchronous request where setting and/or running Client's io_service is required. 222 | /// Do not use concurrently with the synchronous request functions. 223 | void request(const std::string &method, const std::string &path, string_view content, const CaseInsensitiveMultimap &header, 224 | std::function, const error_code &)> &&request_callback_) { 225 | auto session = std::make_shared(config.max_response_streambuf_size, get_connection(), create_request_header(method, path, header)); 226 | auto response = session->response; 227 | auto request_callback = std::make_shared, const error_code &)>>(std::move(request_callback_)); 228 | session->callback = [this, response, request_callback](const std::shared_ptr &connection, const error_code &ec) { 229 | { 230 | std::unique_lock lock(this->connections_mutex); 231 | connection->in_use = false; 232 | 233 | // Remove unused connections, but keep one open for HTTP persistent connection: 234 | std::size_t unused_connections = 0; 235 | for(auto it = this->connections.begin(); it != this->connections.end();) { 236 | if(ec && connection == *it) 237 | it = this->connections.erase(it); 238 | else if((*it)->in_use) 239 | ++it; 240 | else { 241 | ++unused_connections; 242 | if(unused_connections > 1) 243 | it = this->connections.erase(it); 244 | else 245 | ++it; 246 | } 247 | } 248 | } 249 | 250 | if(*request_callback) 251 | (*request_callback)(response, ec); 252 | }; 253 | 254 | std::ostream write_stream(session->request_streambuf.get()); 255 | if(content.size() > 0) 256 | write_stream << "Content-Length: " << content.size() << "\r\n"; 257 | write_stream << "\r\n" 258 | << content; 259 | 260 | connect(session); 261 | } 262 | 263 | /// Asynchronous request where setting and/or running Client's io_service is required. 264 | /// Do not use concurrently with the synchronous request functions. 265 | void request(const std::string &method, const std::string &path, string_view content, 266 | std::function, const error_code &)> &&request_callback) { 267 | request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback)); 268 | } 269 | 270 | /// Asynchronous request where setting and/or running Client's io_service is required. 271 | void request(const std::string &method, const std::string &path, 272 | std::function, const error_code &)> &&request_callback) { 273 | request(method, path, std::string(), CaseInsensitiveMultimap(), std::move(request_callback)); 274 | } 275 | 276 | /// Asynchronous request where setting and/or running Client's io_service is required. 277 | void request(const std::string &method, std::function, const error_code &)> &&request_callback) { 278 | request(method, std::string("/"), std::string(), CaseInsensitiveMultimap(), std::move(request_callback)); 279 | } 280 | 281 | /// Asynchronous request where setting and/or running Client's io_service is required. 282 | void request(const std::string &method, const std::string &path, std::istream &content, const CaseInsensitiveMultimap &header, 283 | std::function, const error_code &)> &&request_callback_) { 284 | auto session = std::make_shared(config.max_response_streambuf_size, get_connection(), create_request_header(method, path, header)); 285 | auto response = session->response; 286 | auto request_callback = std::make_shared, const error_code &)>>(std::move(request_callback_)); 287 | session->callback = [this, response, request_callback](const std::shared_ptr &connection, const error_code &ec) { 288 | { 289 | std::unique_lock lock(this->connections_mutex); 290 | connection->in_use = false; 291 | 292 | // Remove unused connections, but keep one open for HTTP persistent connection: 293 | std::size_t unused_connections = 0; 294 | for(auto it = this->connections.begin(); it != this->connections.end();) { 295 | if(ec && connection == *it) 296 | it = this->connections.erase(it); 297 | else if((*it)->in_use) 298 | ++it; 299 | else { 300 | ++unused_connections; 301 | if(unused_connections > 1) 302 | it = this->connections.erase(it); 303 | else 304 | ++it; 305 | } 306 | } 307 | } 308 | 309 | if(*request_callback) 310 | (*request_callback)(response, ec); 311 | }; 312 | 313 | content.seekg(0, std::ios::end); 314 | auto content_length = content.tellg(); 315 | content.seekg(0, std::ios::beg); 316 | std::ostream write_stream(session->request_streambuf.get()); 317 | if(content_length > 0) 318 | write_stream << "Content-Length: " << content_length << "\r\n"; 319 | write_stream << "\r\n"; 320 | if(content_length > 0) 321 | write_stream << content.rdbuf(); 322 | 323 | connect(session); 324 | } 325 | 326 | /// Asynchronous request where setting and/or running Client's io_service is required. 327 | void request(const std::string &method, const std::string &path, std::istream &content, 328 | std::function, const error_code &)> &&request_callback) { 329 | request(method, path, content, CaseInsensitiveMultimap(), std::move(request_callback)); 330 | } 331 | 332 | /// Close connections 333 | void stop() noexcept { 334 | std::unique_lock lock(connections_mutex); 335 | for(auto it = connections.begin(); it != connections.end();) { 336 | error_code ec; 337 | (*it)->socket->lowest_layer().cancel(ec); 338 | it = connections.erase(it); 339 | } 340 | } 341 | 342 | virtual ~ClientBase() noexcept { 343 | handler_runner->stop(); 344 | stop(); 345 | } 346 | 347 | protected: 348 | bool internal_io_service = false; 349 | 350 | std::string host; 351 | unsigned short port; 352 | 353 | std::unique_ptr query; 354 | 355 | std::unordered_set> connections; 356 | std::mutex connections_mutex; 357 | 358 | std::shared_ptr handler_runner; 359 | 360 | std::size_t concurrent_synchronous_requests = 0; 361 | std::mutex concurrent_synchronous_requests_mutex; 362 | 363 | ClientBase(const std::string &host_port, unsigned short default_port) noexcept : handler_runner(new ScopeRunner()) { 364 | auto parsed_host_port = parse_host_port(host_port, default_port); 365 | host = parsed_host_port.first; 366 | port = parsed_host_port.second; 367 | } 368 | 369 | std::shared_ptr get_connection() noexcept { 370 | std::shared_ptr connection; 371 | std::unique_lock lock(connections_mutex); 372 | 373 | if(!io_service) { 374 | io_service = std::make_shared(); 375 | internal_io_service = true; 376 | } 377 | 378 | for(auto it = connections.begin(); it != connections.end(); ++it) { 379 | if(!(*it)->in_use && !connection) { 380 | connection = *it; 381 | break; 382 | } 383 | } 384 | if(!connection) { 385 | connection = create_connection(); 386 | connections.emplace(connection); 387 | } 388 | connection->attempt_reconnect = true; 389 | connection->in_use = true; 390 | 391 | if(!query) { 392 | if(config.proxy_server.empty()) 393 | query = std::unique_ptr(new asio::ip::tcp::resolver::query(host, std::to_string(port))); 394 | else { 395 | auto proxy_host_port = parse_host_port(config.proxy_server, 8080); 396 | query = std::unique_ptr(new asio::ip::tcp::resolver::query(proxy_host_port.first, std::to_string(proxy_host_port.second))); 397 | } 398 | } 399 | 400 | return connection; 401 | } 402 | 403 | virtual std::shared_ptr create_connection() noexcept = 0; 404 | virtual void connect(const std::shared_ptr &) = 0; 405 | 406 | std::unique_ptr create_request_header(const std::string &method, const std::string &path, const CaseInsensitiveMultimap &header) const { 407 | auto corrected_path = path; 408 | if(corrected_path == "") 409 | corrected_path = "/"; 410 | if(!config.proxy_server.empty() && std::is_same::value) 411 | corrected_path = "http://" + host + ':' + std::to_string(port) + corrected_path; 412 | 413 | std::unique_ptr streambuf(new asio::streambuf()); 414 | std::ostream write_stream(streambuf.get()); 415 | write_stream << method << " " << corrected_path << " HTTP/1.1\r\n"; 416 | write_stream << "Host: " << host << "\r\n"; 417 | for(auto &h : header) 418 | write_stream << h.first << ": " << h.second << "\r\n"; 419 | return streambuf; 420 | } 421 | 422 | std::pair parse_host_port(const std::string &host_port, unsigned short default_port) const noexcept { 423 | std::pair parsed_host_port; 424 | std::size_t host_end = host_port.find(':'); 425 | if(host_end == std::string::npos) { 426 | parsed_host_port.first = host_port; 427 | parsed_host_port.second = default_port; 428 | } 429 | else { 430 | parsed_host_port.first = host_port.substr(0, host_end); 431 | parsed_host_port.second = static_cast(stoul(host_port.substr(host_end + 1))); 432 | } 433 | return parsed_host_port; 434 | } 435 | 436 | void write(const std::shared_ptr &session) { 437 | session->connection->set_timeout(); 438 | asio::async_write(*session->connection->socket, session->request_streambuf->data(), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) { 439 | session->connection->cancel_timeout(); 440 | auto lock = session->connection->handler_runner->continue_lock(); 441 | if(!lock) 442 | return; 443 | if(!ec) 444 | this->read(session); 445 | else 446 | session->callback(session->connection, ec); 447 | }); 448 | } 449 | 450 | void read(const std::shared_ptr &session) { 451 | session->connection->set_timeout(); 452 | asio::async_read_until(*session->connection->socket, session->response->streambuf, "\r\n\r\n", [this, session](const error_code &ec, std::size_t bytes_transferred) { 453 | session->connection->cancel_timeout(); 454 | auto lock = session->connection->handler_runner->continue_lock(); 455 | if(!lock) 456 | return; 457 | if((!ec || ec == asio::error::not_found) && session->response->streambuf.size() == session->response->streambuf.max_size()) { 458 | session->callback(session->connection, make_error_code::make_error_code(errc::message_size)); 459 | return; 460 | } 461 | if(!ec) { 462 | session->connection->attempt_reconnect = true; 463 | std::size_t num_additional_bytes = session->response->streambuf.size() - bytes_transferred; 464 | 465 | if(!ResponseMessage::parse(session->response->content, session->response->http_version, session->response->status_code, session->response->header)) { 466 | session->callback(session->connection, make_error_code::make_error_code(errc::protocol_error)); 467 | return; 468 | } 469 | 470 | auto header_it = session->response->header.find("Content-Length"); 471 | if(header_it != session->response->header.end()) { 472 | auto content_length = stoull(header_it->second); 473 | if(content_length > num_additional_bytes) { 474 | session->connection->set_timeout(); 475 | asio::async_read(*session->connection->socket, session->response->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) { 476 | session->connection->cancel_timeout(); 477 | auto lock = session->connection->handler_runner->continue_lock(); 478 | if(!lock) 479 | return; 480 | if(!ec) { 481 | if(session->response->streambuf.size() == session->response->streambuf.max_size()) { 482 | session->callback(session->connection, make_error_code::make_error_code(errc::message_size)); 483 | return; 484 | } 485 | session->callback(session->connection, ec); 486 | } 487 | else 488 | session->callback(session->connection, ec); 489 | }); 490 | } 491 | else 492 | session->callback(session->connection, ec); 493 | } 494 | else if((header_it = session->response->header.find("Transfer-Encoding")) != session->response->header.end() && header_it->second == "chunked") { 495 | auto tmp_streambuf = std::make_shared(); 496 | this->read_chunked(session, tmp_streambuf); 497 | } 498 | else if(session->response->http_version < "1.1" || ((header_it = session->response->header.find("Session")) != session->response->header.end() && header_it->second == "close")) { 499 | session->connection->set_timeout(); 500 | asio::async_read(*session->connection->socket, session->response->streambuf, [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) { 501 | session->connection->cancel_timeout(); 502 | auto lock = session->connection->handler_runner->continue_lock(); 503 | if(!lock) 504 | return; 505 | if(!ec) { 506 | if(session->response->streambuf.size() == session->response->streambuf.max_size()) { 507 | session->callback(session->connection, make_error_code::make_error_code(errc::message_size)); 508 | return; 509 | } 510 | session->callback(session->connection, ec); 511 | } 512 | else 513 | session->callback(session->connection, ec == asio::error::eof ? error_code() : ec); 514 | }); 515 | } 516 | else 517 | session->callback(session->connection, ec); 518 | } 519 | else { 520 | if(session->connection->attempt_reconnect && ec != asio::error::operation_aborted) { 521 | std::unique_lock lock(connections_mutex); 522 | auto it = connections.find(session->connection); 523 | if(it != connections.end()) { 524 | connections.erase(it); 525 | session->connection = create_connection(); 526 | session->connection->attempt_reconnect = false; 527 | session->connection->in_use = true; 528 | connections.emplace(session->connection); 529 | lock.unlock(); 530 | this->connect(session); 531 | } 532 | else { 533 | lock.unlock(); 534 | session->callback(session->connection, ec); 535 | } 536 | } 537 | else 538 | session->callback(session->connection, ec); 539 | } 540 | }); 541 | } 542 | 543 | void read_chunked(const std::shared_ptr &session, const std::shared_ptr &tmp_streambuf) { 544 | if(tmp_streambuf->size() >= config.max_response_streambuf_size) { 545 | session->callback(session->connection, make_error_code::make_error_code(errc::message_size)); 546 | return; 547 | } 548 | // chunked_streambuf is needed as new read buffer with its size adjusted depending on the size of tmp_streambuf 549 | auto chunked_streambuf = std::make_shared(config.max_response_streambuf_size - tmp_streambuf->size()); 550 | // Move excess read data from session->response->streambuf to chunked_streambuf 551 | if(session->response->streambuf.size() > 0) { 552 | std::ostream chunked_stream(chunked_streambuf.get()); 553 | chunked_stream << &session->response->streambuf; 554 | } 555 | session->connection->set_timeout(); 556 | asio::async_read_until(*session->connection->socket, *chunked_streambuf, "\r\n", [this, session, chunked_streambuf, tmp_streambuf](const error_code &ec, std::size_t bytes_transferred) { 557 | session->connection->cancel_timeout(); 558 | auto lock = session->connection->handler_runner->continue_lock(); 559 | if(!lock) 560 | return; 561 | if((!ec || ec == asio::error::not_found) && chunked_streambuf->size() == chunked_streambuf->max_size()) { 562 | session->callback(session->connection, make_error_code::make_error_code(errc::message_size)); 563 | return; 564 | } 565 | if(!ec) { 566 | std::string line; 567 | std::istream chunked_stream(chunked_streambuf.get()); 568 | getline(chunked_stream, line); 569 | bytes_transferred -= line.size() + 1; 570 | line.pop_back(); 571 | unsigned long length; 572 | try { 573 | length = stoul(line, 0, 16); 574 | } 575 | catch(...) { 576 | session->callback(session->connection, make_error_code::make_error_code(errc::protocol_error)); 577 | return; 578 | } 579 | 580 | auto num_additional_bytes = chunked_streambuf->size() - bytes_transferred; 581 | 582 | auto post_process = [this, session, chunked_streambuf, tmp_streambuf, length]() { 583 | std::istream chunked_stream(chunked_streambuf.get()); 584 | std::ostream tmp_stream(tmp_streambuf.get()); 585 | if(length > 0) { 586 | std::unique_ptr buffer(new char[length]); 587 | chunked_stream.read(buffer.get(), static_cast(length)); 588 | tmp_stream.write(buffer.get(), static_cast(length)); 589 | } 590 | 591 | // Remove "\r\n" 592 | chunked_stream.get(); 593 | chunked_stream.get(); 594 | 595 | if(length > 0) 596 | this->read_chunked(session, tmp_streambuf); 597 | else { 598 | if(tmp_streambuf->size() > 0) { 599 | std::ostream response_stream(&session->response->streambuf); 600 | response_stream << tmp_streambuf.get(); 601 | } 602 | error_code ec; 603 | session->callback(session->connection, ec); 604 | } 605 | }; 606 | 607 | if((2 + length) > num_additional_bytes) { 608 | session->connection->set_timeout(); 609 | asio::async_read(*session->connection->socket, *chunked_streambuf, asio::transfer_exactly(2 + length - num_additional_bytes), [this, session, chunked_streambuf, post_process](const error_code &ec, std::size_t /*bytes_transferred*/) { 610 | session->connection->cancel_timeout(); 611 | auto lock = session->connection->handler_runner->continue_lock(); 612 | if(!lock) 613 | return; 614 | if(!ec) { 615 | if(chunked_streambuf->size() == chunked_streambuf->max_size()) { 616 | session->callback(session->connection, make_error_code::make_error_code(errc::message_size)); 617 | return; 618 | } 619 | post_process(); 620 | } 621 | else 622 | session->callback(session->connection, ec); 623 | }); 624 | } 625 | else 626 | post_process(); 627 | } 628 | else 629 | session->callback(session->connection, ec); 630 | }); 631 | } 632 | }; 633 | 634 | template 635 | class Client : public ClientBase {}; 636 | 637 | using HTTP = asio::ip::tcp::socket; 638 | 639 | template <> 640 | class Client : public ClientBase { 641 | public: 642 | Client(const std::string &server_port_path) noexcept : ClientBase::ClientBase(server_port_path, 80) {} 643 | 644 | protected: 645 | std::shared_ptr create_connection() noexcept override { 646 | return std::make_shared(handler_runner, config.timeout, *io_service); 647 | } 648 | 649 | void connect(const std::shared_ptr &session) override { 650 | if(!session->connection->socket->lowest_layer().is_open()) { 651 | auto resolver = std::make_shared(*io_service); 652 | session->connection->set_timeout(config.timeout_connect); 653 | resolver->async_resolve(*query, [this, session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator it) { 654 | session->connection->cancel_timeout(); 655 | auto lock = session->connection->handler_runner->continue_lock(); 656 | if(!lock) 657 | return; 658 | if(!ec) { 659 | session->connection->set_timeout(config.timeout_connect); 660 | asio::async_connect(*session->connection->socket, it, [this, session, resolver](const error_code &ec, asio::ip::tcp::resolver::iterator /*it*/) { 661 | session->connection->cancel_timeout(); 662 | auto lock = session->connection->handler_runner->continue_lock(); 663 | if(!lock) 664 | return; 665 | if(!ec) { 666 | asio::ip::tcp::no_delay option(true); 667 | error_code ec; 668 | session->connection->socket->set_option(option, ec); 669 | this->write(session); 670 | } 671 | else 672 | session->callback(session->connection, ec); 673 | }); 674 | } 675 | else 676 | session->callback(session->connection, ec); 677 | }); 678 | } 679 | else 680 | write(session); 681 | } 682 | }; 683 | } // namespace SimpleWeb 684 | 685 | #endif /* CLIENT_HTTP_HPP */ 686 | -------------------------------------------------------------------------------- /common.hpp: -------------------------------------------------------------------------------- 1 | // author: tko 2 | #ifndef COMMON_H 3 | #define COMMON_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | void print_hex(const char *label, const uint8_t *v, size_t len) { 12 | size_t i; 13 | 14 | printf("%s: ", label); 15 | for (i = 0; i < len; ++i) { 16 | printf("%02x", v[i]); 17 | } 18 | printf("\n"); 19 | } 20 | 21 | string getMerkleRoot(const vector &merkle) { 22 | printf("\nFinding Merkle Root.... \n"); 23 | if (merkle.empty()) 24 | return ""; 25 | else if (merkle.size() == 1){ 26 | return sha256(merkle[0]); 27 | } 28 | 29 | vector new_merkle = merkle; 30 | 31 | while (new_merkle.size() > 1) { 32 | if ( new_merkle.size() % 2 == 1 ) 33 | new_merkle.push_back(merkle.back()); 34 | 35 | vector result; 36 | 37 | for (int i=0; i < new_merkle.size(); i += 2){ 38 | string var1 = sha256(new_merkle[i]); 39 | string var2 = sha256(new_merkle[i+1]); 40 | string hash = sha256(var1+var2); 41 | // printf("---hash(hash(%s), hash(%s)) => %s\n",new_merkle[0].c_str(),new_merkle[1].c_str(),hash.c_str()); 42 | result.push_back(hash); 43 | } 44 | new_merkle = result; 45 | } 46 | return new_merkle[0]; 47 | 48 | } 49 | pair findHash(int index, string prevHash, vector &merkle) { 50 | string header = to_string(index) + prevHash + getMerkleRoot(merkle); 51 | unsigned int nonce; 52 | for (nonce = 0; nonce < 100000; nonce++ ) { 53 | string blockHash = sha256(header + to_string(nonce)); 54 | if (blockHash.substr(0,2) == "00"){ 55 | // cout << "nonce: " << nonce; 56 | // cout << "header: " << header; 57 | return make_pair(blockHash,to_string(nonce)); 58 | break; 59 | } 60 | } 61 | return make_pair("fail","fail"); 62 | } 63 | // int addBlock(int index, string prevHash, vector &merkle, vector > &blockchain) { 64 | // string header = to_string(index) + prevHash + getMerkleRoot(merkle); 65 | // auto pair = findHash(header); 66 | 67 | // blockchain.push_back(std::make_unique(index,prevHash,pair.first,pair.second,merkle)); 68 | // return 1; 69 | // } 70 | #endif -------------------------------------------------------------------------------- /crypto.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_WEB_CRYPTO_HPP 2 | #define SIMPLE_WEB_CRYPTO_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace SimpleWeb { 17 | // TODO 2017: remove workaround for MSVS 2012 18 | #if _MSC_VER == 1700 // MSVS 2012 has no definition for round() 19 | inline double round(double x) noexcept { // Custom definition of round() for positive numbers 20 | return floor(x + 0.5); 21 | } 22 | #endif 23 | 24 | class Crypto { 25 | const static size_t buffer_size = 131072; 26 | 27 | public: 28 | class Base64 { 29 | public: 30 | static std::string encode(const std::string &ascii) noexcept { 31 | std::string base64; 32 | 33 | BIO *bio, *b64; 34 | BUF_MEM *bptr = BUF_MEM_new(); 35 | 36 | b64 = BIO_new(BIO_f_base64()); 37 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 38 | bio = BIO_new(BIO_s_mem()); 39 | BIO_push(b64, bio); 40 | BIO_set_mem_buf(b64, bptr, BIO_CLOSE); 41 | 42 | // Write directly to base64-buffer to avoid copy 43 | auto base64_length = static_cast(round(4 * ceil(static_cast(ascii.size()) / 3.0))); 44 | base64.resize(base64_length); 45 | bptr->length = 0; 46 | bptr->max = base64_length + 1; 47 | bptr->data = &base64[0]; 48 | 49 | if(BIO_write(b64, &ascii[0], static_cast(ascii.size())) <= 0 || BIO_flush(b64) <= 0) 50 | base64.clear(); 51 | 52 | // To keep &base64[0] through BIO_free_all(b64) 53 | bptr->length = 0; 54 | bptr->max = 0; 55 | bptr->data = nullptr; 56 | 57 | BIO_free_all(b64); 58 | 59 | return base64; 60 | } 61 | 62 | static std::string decode(const std::string &base64) noexcept { 63 | std::string ascii; 64 | 65 | // Resize ascii, however, the size is a up to two bytes too large. 66 | ascii.resize((6 * base64.size()) / 8); 67 | BIO *b64, *bio; 68 | 69 | b64 = BIO_new(BIO_f_base64()); 70 | BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); 71 | bio = BIO_new_mem_buf(&base64[0], static_cast(base64.size())); 72 | bio = BIO_push(b64, bio); 73 | 74 | auto decoded_length = BIO_read(bio, &ascii[0], static_cast(ascii.size())); 75 | if(decoded_length > 0) 76 | ascii.resize(static_cast(decoded_length)); 77 | else 78 | ascii.clear(); 79 | 80 | BIO_free_all(b64); 81 | 82 | return ascii; 83 | } 84 | }; 85 | 86 | /// Return hex string from bytes in input string. 87 | static std::string to_hex_string(const std::string &input) noexcept { 88 | std::stringstream hex_stream; 89 | hex_stream << std::hex << std::internal << std::setfill('0'); 90 | for(auto &byte : input) 91 | hex_stream << std::setw(2) << static_cast(static_cast(byte)); 92 | return hex_stream.str(); 93 | } 94 | 95 | static std::string md5(const std::string &input, size_t iterations = 1) noexcept { 96 | std::string hash; 97 | 98 | hash.resize(128 / 8); 99 | MD5(reinterpret_cast(&input[0]), input.size(), reinterpret_cast(&hash[0])); 100 | 101 | for(size_t c = 1; c < iterations; ++c) 102 | MD5(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 103 | 104 | return hash; 105 | } 106 | 107 | static std::string md5(std::istream &stream, size_t iterations = 1) noexcept { 108 | MD5_CTX context; 109 | MD5_Init(&context); 110 | std::streamsize read_length; 111 | std::vector buffer(buffer_size); 112 | while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) 113 | MD5_Update(&context, buffer.data(), static_cast(read_length)); 114 | std::string hash; 115 | hash.resize(128 / 8); 116 | MD5_Final(reinterpret_cast(&hash[0]), &context); 117 | 118 | for(size_t c = 1; c < iterations; ++c) 119 | MD5(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 120 | 121 | return hash; 122 | } 123 | 124 | static std::string sha1(const std::string &input, size_t iterations = 1) noexcept { 125 | std::string hash; 126 | 127 | hash.resize(160 / 8); 128 | SHA1(reinterpret_cast(&input[0]), input.size(), reinterpret_cast(&hash[0])); 129 | 130 | for(size_t c = 1; c < iterations; ++c) 131 | SHA1(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 132 | 133 | return hash; 134 | } 135 | 136 | static std::string sha1(std::istream &stream, size_t iterations = 1) noexcept { 137 | SHA_CTX context; 138 | SHA1_Init(&context); 139 | std::streamsize read_length; 140 | std::vector buffer(buffer_size); 141 | while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) 142 | SHA1_Update(&context, buffer.data(), static_cast(read_length)); 143 | std::string hash; 144 | hash.resize(160 / 8); 145 | SHA1_Final(reinterpret_cast(&hash[0]), &context); 146 | 147 | for(size_t c = 1; c < iterations; ++c) 148 | SHA1(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 149 | 150 | return hash; 151 | } 152 | 153 | static std::string sha256(const std::string &input, size_t iterations = 1) noexcept { 154 | std::string hash; 155 | 156 | hash.resize(256 / 8); 157 | SHA256(reinterpret_cast(&input[0]), input.size(), reinterpret_cast(&hash[0])); 158 | 159 | for(size_t c = 1; c < iterations; ++c) 160 | SHA256(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 161 | 162 | return hash; 163 | } 164 | 165 | static std::string sha256(std::istream &stream, size_t iterations = 1) noexcept { 166 | SHA256_CTX context; 167 | SHA256_Init(&context); 168 | std::streamsize read_length; 169 | std::vector buffer(buffer_size); 170 | while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) 171 | SHA256_Update(&context, buffer.data(), static_cast(read_length)); 172 | std::string hash; 173 | hash.resize(256 / 8); 174 | SHA256_Final(reinterpret_cast(&hash[0]), &context); 175 | 176 | for(size_t c = 1; c < iterations; ++c) 177 | SHA256(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 178 | 179 | return hash; 180 | } 181 | 182 | static std::string sha512(const std::string &input, size_t iterations = 1) noexcept { 183 | std::string hash; 184 | 185 | hash.resize(512 / 8); 186 | SHA512(reinterpret_cast(&input[0]), input.size(), reinterpret_cast(&hash[0])); 187 | 188 | for(size_t c = 1; c < iterations; ++c) 189 | SHA512(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 190 | 191 | return hash; 192 | } 193 | 194 | static std::string sha512(std::istream &stream, size_t iterations = 1) noexcept { 195 | SHA512_CTX context; 196 | SHA512_Init(&context); 197 | std::streamsize read_length; 198 | std::vector buffer(buffer_size); 199 | while((read_length = stream.read(&buffer[0], buffer_size).gcount()) > 0) 200 | SHA512_Update(&context, buffer.data(), static_cast(read_length)); 201 | std::string hash; 202 | hash.resize(512 / 8); 203 | SHA512_Final(reinterpret_cast(&hash[0]), &context); 204 | 205 | for(size_t c = 1; c < iterations; ++c) 206 | SHA512(reinterpret_cast(&hash[0]), hash.size(), reinterpret_cast(&hash[0])); 207 | 208 | return hash; 209 | } 210 | 211 | /// key_size is number of bytes of the returned key. 212 | static std::string pbkdf2(const std::string &password, const std::string &salt, int iterations, int key_size) noexcept { 213 | std::string key; 214 | key.resize(static_cast(key_size)); 215 | PKCS5_PBKDF2_HMAC_SHA1(password.c_str(), password.size(), 216 | reinterpret_cast(salt.c_str()), salt.size(), iterations, 217 | key_size, reinterpret_cast(&key[0])); 218 | return key; 219 | } 220 | }; 221 | } 222 | #endif /* SIMPLE_WEB_CRYPTO_HPP */ 223 | -------------------------------------------------------------------------------- /hash.hpp: -------------------------------------------------------------------------------- 1 | //author: tko 2 | #ifndef _HASH_H_ 3 | #define _HASH_H_ 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | using namespace std; 12 | 13 | 14 | string sha256(const string str) { 15 | unsigned char hash[SHA256_DIGEST_LENGTH]; 16 | SHA256_CTX sha256; 17 | SHA256_Init(&sha256); 18 | SHA256_Update(&sha256, str.c_str(), str.size()); 19 | SHA256_Final(hash, &sha256); 20 | 21 | stringstream ss; 22 | for(int i = 0; i < SHA256_DIGEST_LENGTH; i++) 23 | { 24 | ss << hex << setw(2) << setfill('0') << (int)hash[i]; 25 | } 26 | 27 | return ss.str(); 28 | } 29 | 30 | #endif -------------------------------------------------------------------------------- /js_client_test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BlockChain WebSocket test 6 | 7 | 8 | 21 | 22 | -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | LICENSE ISSUES 2 | ============== 3 | 4 | The OpenSSL toolkit stays under a double license, i.e. both the conditions of 5 | the OpenSSL License and the original SSLeay license apply to the toolkit. 6 | See below for the actual license texts. 7 | 8 | OpenSSL License 9 | --------------- 10 | 11 | /* ==================================================================== 12 | * Copyright (c) 1998-2017 The OpenSSL Project. All rights reserved. 13 | * 14 | * Redistribution and use in source and binary forms, with or without 15 | * modification, are permitted provided that the following conditions 16 | * are met: 17 | * 18 | * 1. Redistributions of source code must retain the above copyright 19 | * notice, this list of conditions and the following disclaimer. 20 | * 21 | * 2. Redistributions in binary form must reproduce the above copyright 22 | * notice, this list of conditions and the following disclaimer in 23 | * the documentation and/or other materials provided with the 24 | * distribution. 25 | * 26 | * 3. All advertising materials mentioning features or use of this 27 | * software must display the following acknowledgment: 28 | * "This product includes software developed by the OpenSSL Project 29 | * for use in the OpenSSL Toolkit. (http://www.openssl.org/)" 30 | * 31 | * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to 32 | * endorse or promote products derived from this software without 33 | * prior written permission. For written permission, please contact 34 | * openssl-core@openssl.org. 35 | * 36 | * 5. Products derived from this software may not be called "OpenSSL" 37 | * nor may "OpenSSL" appear in their names without prior written 38 | * permission of the OpenSSL Project. 39 | * 40 | * 6. Redistributions of any form whatsoever must retain the following 41 | * acknowledgment: 42 | * "This product includes software developed by the OpenSSL Project 43 | * for use in the OpenSSL Toolkit (http://www.openssl.org/)" 44 | * 45 | * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY 46 | * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 47 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 48 | * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR 49 | * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 50 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 51 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 52 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 53 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 54 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 55 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 56 | * OF THE POSSIBILITY OF SUCH DAMAGE. 57 | * ==================================================================== 58 | * 59 | * This product includes cryptographic software written by Eric Young 60 | * (eay@cryptsoft.com). This product includes software written by Tim 61 | * Hudson (tjh@cryptsoft.com). 62 | * 63 | */ 64 | 65 | Original SSLeay License 66 | ----------------------- 67 | 68 | /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) 69 | * All rights reserved. 70 | * 71 | * This package is an SSL implementation written 72 | * by Eric Young (eay@cryptsoft.com). 73 | * The implementation was written so as to conform with Netscapes SSL. 74 | * 75 | * This library is free for commercial and non-commercial use as long as 76 | * the following conditions are aheared to. The following conditions 77 | * apply to all code found in this distribution, be it the RC4, RSA, 78 | * lhash, DES, etc., code; not just the SSL code. The SSL documentation 79 | * included with this distribution is covered by the same copyright terms 80 | * except that the holder is Tim Hudson (tjh@cryptsoft.com). 81 | * 82 | * Copyright remains Eric Young's, and as such any Copyright notices in 83 | * the code are not to be removed. 84 | * If this package is used in a product, Eric Young should be given attribution 85 | * as the author of the parts of the library used. 86 | * This can be in the form of a textual message at program startup or 87 | * in documentation (online or textual) provided with the package. 88 | * 89 | * Redistribution and use in source and binary forms, with or without 90 | * modification, are permitted provided that the following conditions 91 | * are met: 92 | * 1. Redistributions of source code must retain the copyright 93 | * notice, this list of conditions and the following disclaimer. 94 | * 2. Redistributions in binary form must reproduce the above copyright 95 | * notice, this list of conditions and the following disclaimer in the 96 | * documentation and/or other materials provided with the distribution. 97 | * 3. All advertising materials mentioning features or use of this software 98 | * must display the following acknowledgement: 99 | * "This product includes cryptographic software written by 100 | * Eric Young (eay@cryptsoft.com)" 101 | * The word 'cryptographic' can be left out if the rouines from the library 102 | * being used are not cryptographic related :-). 103 | * 4. If you include any Windows specific code (or a derivative thereof) from 104 | * the apps directory (application code) you must include an acknowledgement: 105 | * "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" 106 | * 107 | * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND 108 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 109 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 110 | * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 111 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 112 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 113 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 114 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 115 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 116 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 117 | * SUCH DAMAGE. 118 | * 119 | * The licence and distribution terms for any publically available version or 120 | * derivative of this code cannot be changed. i.e. this code cannot simply be 121 | * copied and put under another distribution licence 122 | * [including the GNU Public Licence.] 123 | */ 124 | 125 | The MIT License (MIT) 126 | 127 | Copyright (c) 2014-2016 Ole Christian Eidheim 128 | 129 | Permission is hereby granted, free of charge, to any person obtaining a copy 130 | of this software and associated documentation files (the "Software"), to deal 131 | in the Software without restriction, including without limitation the rights 132 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 133 | copies of the Software, and to permit persons to whom the Software is 134 | furnished to do so, subject to the following conditions: 135 | 136 | The above copyright notice and this permission notice shall be included in all 137 | copies or substantial portions of the Software. 138 | 139 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 140 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 141 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 142 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 143 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 144 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 145 | SOFTWARE. 146 | 147 | MIT License 148 | 149 | Copyright (c) 2013-2017 Niels Lohmann 150 | 151 | Permission is hereby granted, free of charge, to any person obtaining a copy 152 | of this software and associated documentation files (the "Software"), to deal 153 | in the Software without restriction, including without limitation the rights 154 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 155 | copies of the Software, and to permit persons to whom the Software is 156 | furnished to do so, subject to the following conditions: 157 | 158 | The above copyright notice and this permission notice shall be included in all 159 | copies or substantial portions of the Software. 160 | 161 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 162 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 163 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 164 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 165 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 166 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 167 | SOFTWARE. -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | // author: tko 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "hash.hpp" 10 | #include "Block.hpp" 11 | #include "common.hpp" 12 | #include "BlockChain.hpp" 13 | #include "requests.hpp" 14 | 15 | #include "json.hh" 16 | using json = nlohmann::json; 17 | 18 | using namespace std; 19 | 20 | #include "client_http.hpp" 21 | #include "server_http.hpp" 22 | 23 | using HttpServer = SimpleWeb::Server; 24 | using HttpClient = SimpleWeb::Client; 25 | 26 | /* 27 | Hash header: index + prevHash + merkleRoot(data) + nonce 28 | */ 29 | 30 | 31 | /* 32 | * Main function - sets up server, command line interface 33 | */ 34 | int main() { 35 | printf("Welcome! To quit-> Control c \n"); 36 | HttpServer server; 37 | 38 | // Set up ports 39 | 40 | int port; 41 | printf("Enter port: "); 42 | scanf("%d",&port); 43 | server.config.port = port; //server port 44 | 45 | vector listOfNodes; //vector of the ports of nodes in the network 46 | 47 | // BLOCK CHAIN INITIALIZATION AND ADDING SELF TO NETWORK 48 | 49 | char ch; 50 | printf("Are you the initial Node? (y or n) "); 51 | scanf(" %c",&ch); 52 | BlockChain bc; 53 | if (ch == 'y'){ 54 | // Initial Node: setup Blockchain with genesis block 55 | bc = BlockChain(0); 56 | } 57 | else if(ch =='n'){ 58 | // New Node - need to add self to network by providing ports 59 | bc = BlockChain(0); 60 | char otherPorts[50]; 61 | // Example input: 8000,3000,3030 62 | printf("Enter ports of nodes in network(with commas in between): "); 63 | scanf("%s",otherPorts); 64 | stringstream ss(otherPorts); 65 | int i; 66 | // parse string of nodes and add them to listOfNoes 67 | while (ss >> i) 68 | { 69 | listOfNodes.push_back(i); 70 | if (ss.peek() == ',' || ss.peek() == ' ') 71 | ss.ignore(); 72 | } 73 | addSelfToNetwork(&listOfNodes,server.config.port); 74 | json chain = getChainFromNodes(&listOfNodes); 75 | //skips first block - same genesis block across all nodes 76 | for (int a = 1; a (); a++ ){ 77 | auto block = chain["data"][a]; 78 | vector data = block["data"].get >(); 79 | bc.addBlock(block["index"],block["previousHash"],block["hash"],block["nonce"],data); 80 | } 81 | } 82 | else { 83 | return 0; 84 | } 85 | 86 | // SERVER INITIALIZATION 87 | 88 | 89 | /* POST /addnode - used to add node to network, called by new node to all the nodes in the network 90 | * adds node(port) to listOfNodes 91 | */ 92 | server.resource["^/addnode$"]["POST"] = [&listOfNodes](shared_ptr response, shared_ptr request) { 93 | printf("POST /addnode --- New Node adding to network....\n"); 94 | try { 95 | json content = json::parse(request->content); 96 | int port = content["port"].get(); 97 | listOfNodes.push_back(port); // Adds port to listOfNodes 98 | printf("----Adding node %d to listOfNodes\n",port); 99 | response->write("Added You to our List"); 100 | } 101 | catch(const exception &e) { 102 | *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what(); 103 | } 104 | }; 105 | 106 | /* GET /latestchain gets latest blockchain and sends it*/ 107 | server.resource["^/latestchain$"]["GET"] = [&bc](shared_ptr response, shared_ptr request) { 108 | printf("GET /latestchain --- Sending BlockChain....\n"); 109 | response->write(bc.toJSON()); 110 | printf("---Sent current BlockChain\n"); 111 | }; 112 | 113 | /* POST /newchain called by a node when a new block is added to it - 114 | * checks whether the length of the blockchain is bigger than our own blockchain 115 | * if it is bigger -> replace chain, else don't do anything 116 | */ 117 | server.resource["^/newchain$"]["POST"] = [&bc](shared_ptr response, shared_ptr request) { 118 | cout << "POST /newchain --- Node in Network sent new chain\n"; 119 | try { 120 | json content = json::parse(request->content); 121 | if (content["length"].get() > bc.getNumOfBlocks()){ 122 | bc.replaceChain(content); 123 | cout << "----Replaced current chain with new one" << endl; 124 | response->write("Replaced Chain\n"); 125 | } 126 | else { 127 | cout << "----Chain was not replaced: sent chain had same size" <write("Same Chain Size -- invalid"); 129 | } 130 | } 131 | catch(const exception &e) { 132 | *response << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what(); 133 | } 134 | }; 135 | 136 | // On error lambda function 137 | server.on_error = [](shared_ptr /*request*/, const SimpleWeb::error_code & ec) { 138 | if (ec.message() != "End of file") { 139 | cout << "SERVER ERROR: " << ec.message() << endl; 140 | } 141 | }; 142 | printf("Starting server at %d",server.config.port); 143 | 144 | // start server 145 | thread server_thread([&server]() { 146 | server.start(); 147 | }); 148 | 149 | //COMMAND LINE INTERFACE 150 | 151 | // loop for 20 inputs - can change 152 | for ( int i = 0; i < 20; i++ ) { 153 | vector v; 154 | int temp; 155 | // ask for what to do 156 | printf("\n(1) Look at Blocks \n(2) Add block\n"); 157 | int valid = scanf("%d",&temp); 158 | 159 | if ( (valid == 1) && (temp == 1)){ // queue up block if 1 160 | printf("What Block do you want to look at? "); 161 | scanf("%d",&temp); 162 | try { 163 | bc.getBlock(temp).toString(); 164 | } 165 | catch (const exception& e){ 166 | cout << e.what() << endl; 167 | } 168 | } 169 | else if (temp == 2){ // add a new block if 2 170 | char tmp[201]; 171 | printf("\nADDING BLOCKS!\nEnter your message: "); 172 | scanf("%200s",tmp); 173 | string str = tmp; 174 | printf("Entered '%s' into block\n",str.c_str()); 175 | v.push_back(str); 176 | 177 | int in; 178 | printf("Press any number to add block to blockchain: "); 179 | scanf("%d",&in); 180 | 181 | try { 182 | if (bc.getNumOfBlocks() == 0) { 183 | printf("----------------------------------\nPlease join the network... Your blockchain doesn't have any blocks "); 184 | continue; 185 | } 186 | // mine for the has 187 | auto pair = findHash(bc.getNumOfBlocks(),bc.getLatestBlockHash(),v); 188 | // add the block to the blockchain 189 | bc.addBlock(bc.getNumOfBlocks(),bc.getLatestBlockHash(),pair.first,pair.second,v ); 190 | // send the blockchain to the network 191 | sendNewChain(&listOfNodes,bc.toJSON()); 192 | } 193 | catch (const exception& e) { 194 | cout << e.what() << "\n" << endl; 195 | } 196 | } 197 | } 198 | 199 | // bc.addBlock(0,string("00000000000000"),string("003d9dc40cad6b414d45555e4b83045cfde74bcee6b09fb42536ca2500087fd9"),string("46"),v); 200 | printf("\n"); 201 | return 0; 202 | } 203 | 204 | -------------------------------------------------------------------------------- /requests.hpp: -------------------------------------------------------------------------------- 1 | //author: tko 2 | #ifndef REQUESTS_H 3 | #define REQUESTS_H 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "json.hh" 13 | using json = nlohmann::json; 14 | 15 | using namespace std; 16 | 17 | #include "client_http.hpp" 18 | 19 | using HttpClient = SimpleWeb::Client; 20 | 21 | 22 | /* 23 | * Client request functions 24 | */ 25 | /* 26 | * gets the latest chain from the network and finds the longest one 27 | * used when node initializes 28 | * Returns: JSON object of the blockchain 29 | */ 30 | json getChainFromNodes(const vector *listOfNodes){ 31 | printf("Pinging nodes for chains....\n"); 32 | vector vect; 33 | for ( int a = 0; a < (*listOfNodes).size(); a++ ){ 34 | int port = (*listOfNodes)[a]; 35 | printf("--- pinging node %d\n",port); 36 | HttpClient client("localhost:"+to_string(port)); 37 | try { 38 | auto req = client.request("GET", "/latestchain"); 39 | vect.push_back(req->content.string()); 40 | } 41 | catch(const SimpleWeb::system_error &e) { 42 | cerr << "Client request error: " << e.what() << endl; 43 | } 44 | } 45 | 46 | //find biggest blockchain 47 | json biggest_bc = json::parse(vect[0]); 48 | int max = 0; 49 | for (int i =0; i < vect.size(); i++) { 50 | auto json_data = json::parse(vect[i]); 51 | if ( max < json_data["length"].get() ){ 52 | max = json_data["length"].get(); 53 | biggest_bc = json_data; 54 | } 55 | } 56 | return biggest_bc; 57 | 58 | } 59 | 60 | /* 61 | * sends out the new blockchain to network 62 | */ 63 | void sendNewChain(const vector *listOfNodes, string json){ 64 | printf("Sending new chain to network....\n"); 65 | for ( int a = 0; a < (*listOfNodes).size(); a++ ){ 66 | int port = (*listOfNodes)[a]; 67 | printf("--- sending to node %d\n",port); 68 | HttpClient client("localhost:" + to_string(port)); 69 | try { 70 | auto req = client.request("POST", "/newchain",json); 71 | cout << "Node " << port << " Response: " << req->content.string() << endl; 72 | } 73 | catch(const SimpleWeb::system_error &e) { 74 | cerr << "Client request error: " << e.what() << endl; 75 | } 76 | } 77 | } 78 | /* 79 | * adds self to network 80 | * called when node initializes - used so nodes could keep track of nodes in the network 81 | */ 82 | void addSelfToNetwork(const vector *listOfNodes,int port) { 83 | printf("Sending port to all nodes\n"); 84 | json j; 85 | j["port"] = port; 86 | for ( int a = 0; a < (*listOfNodes).size(); a++ ){ 87 | int port = (*listOfNodes)[a]; 88 | printf("--- sending port to node %d\n",port); 89 | HttpClient client("localhost:"+to_string(port)); 90 | try { 91 | auto req = client.request("POST","/addnode",j.dump(3)); 92 | cout << "Node " << port << " Reponse: " << req->content.string() << endl; 93 | } 94 | catch(const SimpleWeb::system_error &e) { 95 | cerr << "Client request error: " << e.what() << endl; 96 | } 97 | } 98 | } 99 | 100 | 101 | 102 | 103 | #endif -------------------------------------------------------------------------------- /server_http.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SERVER_HTTP_HPP 2 | #define SERVER_HTTP_HPP 3 | 4 | #include "utility.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #ifdef USE_STANDALONE_ASIO 15 | #include 16 | #include 17 | namespace SimpleWeb { 18 | using error_code = std::error_code; 19 | using errc = std::errc; 20 | namespace make_error_code = std; 21 | } // namespace SimpleWeb 22 | #else 23 | #include 24 | #include 25 | namespace SimpleWeb { 26 | namespace asio = boost::asio; 27 | using error_code = boost::system::error_code; 28 | namespace errc = boost::system::errc; 29 | namespace make_error_code = boost::system::errc; 30 | } // namespace SimpleWeb 31 | #endif 32 | 33 | // Late 2017 TODO: remove the following checks and always use std::regex 34 | #ifdef USE_BOOST_REGEX 35 | #include 36 | namespace SimpleWeb { 37 | namespace regex = boost; 38 | } 39 | #else 40 | #include 41 | namespace SimpleWeb { 42 | namespace regex = std; 43 | } 44 | #endif 45 | 46 | namespace SimpleWeb { 47 | template 48 | class Server; 49 | 50 | template 51 | class ServerBase { 52 | protected: 53 | class Session; 54 | 55 | public: 56 | class Response : public std::enable_shared_from_this, public std::ostream { 57 | friend class ServerBase; 58 | friend class Server; 59 | 60 | asio::streambuf streambuf; 61 | 62 | std::shared_ptr session; 63 | long timeout_content; 64 | 65 | Response(std::shared_ptr session, long timeout_content) noexcept : std::ostream(&streambuf), session(std::move(session)), timeout_content(timeout_content) {} 66 | 67 | template 68 | void write_header(const CaseInsensitiveMultimap &header, size_type size) { 69 | bool content_length_written = false; 70 | bool chunked_transfer_encoding = false; 71 | for(auto &field : header) { 72 | if(!content_length_written && case_insensitive_equal(field.first, "content-length")) 73 | content_length_written = true; 74 | else if(!chunked_transfer_encoding && case_insensitive_equal(field.first, "transfer-encoding") && case_insensitive_equal(field.second, "chunked")) 75 | chunked_transfer_encoding = true; 76 | 77 | *this << field.first << ": " << field.second << "\r\n"; 78 | } 79 | if(!content_length_written && !chunked_transfer_encoding && !close_connection_after_response) 80 | *this << "Content-Length: " << size << "\r\n\r\n"; 81 | else 82 | *this << "\r\n"; 83 | } 84 | 85 | public: 86 | std::size_t size() noexcept { 87 | return streambuf.size(); 88 | } 89 | 90 | /// Use this function if you need to recursively send parts of a longer message 91 | void send(const std::function &callback = nullptr) noexcept { 92 | session->connection->set_timeout(timeout_content); 93 | auto self = this->shared_from_this(); // Keep Response instance alive through the following async_write 94 | asio::async_write(*session->connection->socket, streambuf, [self, callback](const error_code &ec, std::size_t /*bytes_transferred*/) { 95 | self->session->connection->cancel_timeout(); 96 | auto lock = self->session->connection->handler_runner->continue_lock(); 97 | if(!lock) 98 | return; 99 | if(callback) 100 | callback(ec); 101 | }); 102 | } 103 | 104 | /// Write directly to stream buffer using std::ostream::write 105 | void write(const char_type *ptr, std::streamsize n) { 106 | std::ostream::write(ptr, n); 107 | } 108 | 109 | /// Convenience function for writing status line, potential header fields, and empty content 110 | void write(StatusCode status_code = StatusCode::success_ok, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 111 | *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; 112 | write_header(header, 0); 113 | } 114 | 115 | /// Convenience function for writing status line, header fields, and content 116 | void write(StatusCode status_code, const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 117 | *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; 118 | write_header(header, content.size()); 119 | if(!content.empty()) 120 | *this << content; 121 | } 122 | 123 | /// Convenience function for writing status line, header fields, and content 124 | void write(StatusCode status_code, std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 125 | *this << "HTTP/1.1 " << SimpleWeb::status_code(status_code) << "\r\n"; 126 | content.seekg(0, std::ios::end); 127 | auto size = content.tellg(); 128 | content.seekg(0, std::ios::beg); 129 | write_header(header, size); 130 | if(size) 131 | *this << content.rdbuf(); 132 | } 133 | 134 | /// Convenience function for writing success status line, header fields, and content 135 | void write(const std::string &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 136 | write(StatusCode::success_ok, content, header); 137 | } 138 | 139 | /// Convenience function for writing success status line, header fields, and content 140 | void write(std::istream &content, const CaseInsensitiveMultimap &header = CaseInsensitiveMultimap()) { 141 | write(StatusCode::success_ok, content, header); 142 | } 143 | 144 | /// Convenience function for writing success status line, and header fields 145 | void write(const CaseInsensitiveMultimap &header) { 146 | write(StatusCode::success_ok, std::string(), header); 147 | } 148 | 149 | /// If true, force server to close the connection after the response have been sent. 150 | /// 151 | /// This is useful when implementing a HTTP/1.0-server sending content 152 | /// without specifying the content length. 153 | bool close_connection_after_response = false; 154 | }; 155 | 156 | class Content : public std::istream { 157 | friend class ServerBase; 158 | 159 | public: 160 | std::size_t size() noexcept { 161 | return streambuf.size(); 162 | } 163 | /// Convenience function to return std::string. The stream buffer is consumed. 164 | std::string string() noexcept { 165 | try { 166 | std::stringstream ss; 167 | ss << rdbuf(); 168 | return ss.str(); 169 | } 170 | catch(...) { 171 | return std::string(); 172 | } 173 | } 174 | 175 | private: 176 | asio::streambuf &streambuf; 177 | Content(asio::streambuf &streambuf) noexcept : std::istream(&streambuf), streambuf(streambuf) {} 178 | }; 179 | 180 | class Request { 181 | friend class ServerBase; 182 | friend class Server; 183 | friend class Session; 184 | 185 | asio::streambuf streambuf; 186 | Request(std::size_t max_request_streambuf_size, const std::string &remote_endpoint_address = std::string(), unsigned short remote_endpoint_port = 0) noexcept 187 | : streambuf(max_request_streambuf_size), content(streambuf), remote_endpoint_address(remote_endpoint_address), remote_endpoint_port(remote_endpoint_port) {} 188 | 189 | public: 190 | std::string method, path, query_string, http_version; 191 | 192 | Content content; 193 | 194 | CaseInsensitiveMultimap header; 195 | 196 | regex::smatch path_match; 197 | 198 | std::string remote_endpoint_address; 199 | unsigned short remote_endpoint_port; 200 | 201 | /// Returns query keys with percent-decoded values. 202 | CaseInsensitiveMultimap parse_query_string() noexcept { 203 | return SimpleWeb::QueryString::parse(query_string); 204 | } 205 | }; 206 | 207 | protected: 208 | class Connection : public std::enable_shared_from_this { 209 | public: 210 | template 211 | Connection(std::shared_ptr handler_runner, Args &&... args) noexcept : handler_runner(std::move(handler_runner)), socket(new socket_type(std::forward(args)...)) {} 212 | 213 | std::shared_ptr handler_runner; 214 | 215 | std::unique_ptr socket; // Socket must be unique_ptr since asio::ssl::stream is not movable 216 | std::mutex socket_close_mutex; 217 | 218 | std::unique_ptr timer; 219 | 220 | void close() noexcept { 221 | error_code ec; 222 | std::unique_lock lock(socket_close_mutex); // The following operations seems to be needed to run sequentially 223 | socket->lowest_layer().shutdown(asio::ip::tcp::socket::shutdown_both, ec); 224 | socket->lowest_layer().close(ec); 225 | } 226 | 227 | void set_timeout(long seconds) noexcept { 228 | if(seconds == 0) { 229 | timer = nullptr; 230 | return; 231 | } 232 | 233 | timer = std::unique_ptr(new asio::steady_timer(socket->get_io_service())); 234 | timer->expires_from_now(std::chrono::seconds(seconds)); 235 | auto self = this->shared_from_this(); 236 | timer->async_wait([self](const error_code &ec) { 237 | if(!ec) 238 | self->close(); 239 | }); 240 | } 241 | 242 | void cancel_timeout() noexcept { 243 | if(timer) { 244 | error_code ec; 245 | timer->cancel(ec); 246 | } 247 | } 248 | }; 249 | 250 | class Session { 251 | public: 252 | Session(std::size_t max_request_streambuf_size, std::shared_ptr connection) noexcept : connection(std::move(connection)) { 253 | try { 254 | auto remote_endpoint = this->connection->socket->lowest_layer().remote_endpoint(); 255 | request = std::shared_ptr(new Request(max_request_streambuf_size, remote_endpoint.address().to_string(), remote_endpoint.port())); 256 | } 257 | catch(...) { 258 | request = std::shared_ptr(new Request(max_request_streambuf_size)); 259 | } 260 | } 261 | 262 | std::shared_ptr connection; 263 | std::shared_ptr request; 264 | }; 265 | 266 | public: 267 | class Config { 268 | friend class ServerBase; 269 | 270 | Config(unsigned short port) noexcept : port(port) {} 271 | 272 | public: 273 | /// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS. 274 | unsigned short port; 275 | /// If io_service is not set, number of threads that the server will use when start() is called. 276 | /// Defaults to 1 thread. 277 | std::size_t thread_pool_size = 1; 278 | /// Timeout on request handling. Defaults to 5 seconds. 279 | long timeout_request = 5; 280 | /// Timeout on content handling. Defaults to 300 seconds. 281 | long timeout_content = 300; 282 | /// Maximum size of request stream buffer. Defaults to architecture maximum. 283 | /// Reaching this limit will result in a message_size error code. 284 | std::size_t max_request_streambuf_size = std::numeric_limits::max(); 285 | /// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation. 286 | /// If empty, the address will be any address. 287 | std::string address; 288 | /// Set to false to avoid binding the socket to an address that is already in use. Defaults to true. 289 | bool reuse_address = true; 290 | }; 291 | /// Set before calling start(). 292 | Config config; 293 | 294 | private: 295 | class regex_orderable : public regex::regex { 296 | std::string str; 297 | 298 | public: 299 | regex_orderable(const char *regex_cstr) : regex::regex(regex_cstr), str(regex_cstr) {} 300 | regex_orderable(std::string regex_str) : regex::regex(regex_str), str(std::move(regex_str)) {} 301 | bool operator<(const regex_orderable &rhs) const noexcept { 302 | return str < rhs.str; 303 | } 304 | }; 305 | 306 | public: 307 | /// Warning: do not add or remove resources after start() is called 308 | std::map::Response>, std::shared_ptr::Request>)>>> resource; 309 | 310 | std::map::Response>, std::shared_ptr::Request>)>> default_resource; 311 | 312 | std::function::Request>, const error_code &)> on_error; 313 | 314 | std::function &, std::shared_ptr::Request>)> on_upgrade; 315 | 316 | /// If you have your own asio::io_service, store its pointer here before running start(). 317 | std::shared_ptr io_service; 318 | 319 | virtual void start() { 320 | if(!io_service) { 321 | io_service = std::make_shared(); 322 | internal_io_service = true; 323 | } 324 | 325 | if(io_service->stopped()) 326 | io_service->reset(); 327 | 328 | asio::ip::tcp::endpoint endpoint; 329 | if(config.address.size() > 0) 330 | endpoint = asio::ip::tcp::endpoint(asio::ip::address::from_string(config.address), config.port); 331 | else 332 | endpoint = asio::ip::tcp::endpoint(asio::ip::tcp::v4(), config.port); 333 | 334 | if(!acceptor) 335 | acceptor = std::unique_ptr(new asio::ip::tcp::acceptor(*io_service)); 336 | acceptor->open(endpoint.protocol()); 337 | acceptor->set_option(asio::socket_base::reuse_address(config.reuse_address)); 338 | acceptor->bind(endpoint); 339 | acceptor->listen(); 340 | 341 | accept(); 342 | 343 | if(internal_io_service) { 344 | // If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling 345 | threads.clear(); 346 | for(std::size_t c = 1; c < config.thread_pool_size; c++) { 347 | threads.emplace_back([this]() { 348 | this->io_service->run(); 349 | }); 350 | } 351 | 352 | // Main thread 353 | if(config.thread_pool_size > 0) 354 | io_service->run(); 355 | 356 | // Wait for the rest of the threads, if any, to finish as well 357 | for(auto &t : threads) 358 | t.join(); 359 | } 360 | } 361 | 362 | /// Stop accepting new requests, and close current connections. 363 | void stop() noexcept { 364 | if(acceptor) { 365 | error_code ec; 366 | acceptor->close(ec); 367 | 368 | { 369 | std::unique_lock lock(*connections_mutex); 370 | for(auto &connection : *connections) 371 | connection->close(); 372 | connections->clear(); 373 | } 374 | 375 | if(internal_io_service) 376 | io_service->stop(); 377 | } 378 | } 379 | 380 | virtual ~ServerBase() noexcept { 381 | handler_runner->stop(); 382 | stop(); 383 | } 384 | 385 | protected: 386 | bool internal_io_service = false; 387 | 388 | std::unique_ptr acceptor; 389 | std::vector threads; 390 | 391 | std::shared_ptr> connections; 392 | std::shared_ptr connections_mutex; 393 | 394 | std::shared_ptr handler_runner; 395 | 396 | ServerBase(unsigned short port) noexcept : config(port), connections(new std::unordered_set()), connections_mutex(new std::mutex()), handler_runner(new ScopeRunner()) {} 397 | 398 | virtual void accept() = 0; 399 | 400 | template 401 | std::shared_ptr create_connection(Args &&... args) noexcept { 402 | auto connections = this->connections; 403 | auto connections_mutex = this->connections_mutex; 404 | auto connection = std::shared_ptr(new Connection(handler_runner, std::forward(args)...), [connections, connections_mutex](Connection *connection) { 405 | { 406 | std::unique_lock lock(*connections_mutex); 407 | auto it = connections->find(connection); 408 | if(it != connections->end()) 409 | connections->erase(it); 410 | } 411 | delete connection; 412 | }); 413 | { 414 | std::unique_lock lock(*connections_mutex); 415 | connections->emplace(connection.get()); 416 | } 417 | return connection; 418 | } 419 | 420 | void read_request_and_content(const std::shared_ptr &session) { 421 | session->connection->set_timeout(config.timeout_request); 422 | asio::async_read_until(*session->connection->socket, session->request->streambuf, "\r\n\r\n", [this, session](const error_code &ec, std::size_t bytes_transferred) { 423 | session->connection->cancel_timeout(); 424 | auto lock = session->connection->handler_runner->continue_lock(); 425 | if(!lock) 426 | return; 427 | if((!ec || ec == asio::error::not_found) && session->request->streambuf.size() == session->request->streambuf.max_size()) { 428 | auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); 429 | response->write(StatusCode::client_error_payload_too_large); 430 | response->send(); 431 | if(this->on_error) 432 | this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); 433 | return; 434 | } 435 | if(!ec) { 436 | // request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs: 437 | // "After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter" 438 | // The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the 439 | // streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content). 440 | std::size_t num_additional_bytes = session->request->streambuf.size() - bytes_transferred; 441 | 442 | if(!RequestMessage::parse(session->request->content, session->request->method, session->request->path, 443 | session->request->query_string, session->request->http_version, session->request->header)) { 444 | if(this->on_error) 445 | this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error)); 446 | return; 447 | } 448 | 449 | // If content, read that as well 450 | auto it = session->request->header.find("Content-Length"); 451 | if(it != session->request->header.end()) { 452 | unsigned long long content_length = 0; 453 | try { 454 | content_length = stoull(it->second); 455 | } 456 | catch(const std::exception &e) { 457 | if(this->on_error) 458 | this->on_error(session->request, make_error_code::make_error_code(errc::protocol_error)); 459 | return; 460 | } 461 | if(content_length > num_additional_bytes) { 462 | session->connection->set_timeout(config.timeout_content); 463 | asio::async_read(*session->connection->socket, session->request->streambuf, asio::transfer_exactly(content_length - num_additional_bytes), [this, session](const error_code &ec, std::size_t /*bytes_transferred*/) { 464 | session->connection->cancel_timeout(); 465 | auto lock = session->connection->handler_runner->continue_lock(); 466 | if(!lock) 467 | return; 468 | if(!ec) { 469 | if(session->request->streambuf.size() == session->request->streambuf.max_size()) { 470 | auto response = std::shared_ptr(new Response(session, this->config.timeout_content)); 471 | response->write(StatusCode::client_error_payload_too_large); 472 | response->send(); 473 | if(this->on_error) 474 | this->on_error(session->request, make_error_code::make_error_code(errc::message_size)); 475 | return; 476 | } 477 | this->find_resource(session); 478 | } 479 | else if(this->on_error) 480 | this->on_error(session->request, ec); 481 | }); 482 | } 483 | else 484 | this->find_resource(session); 485 | } 486 | else 487 | this->find_resource(session); 488 | } 489 | else if(this->on_error) 490 | this->on_error(session->request, ec); 491 | }); 492 | } 493 | 494 | void find_resource(const std::shared_ptr &session) { 495 | // Upgrade connection 496 | if(on_upgrade) { 497 | auto it = session->request->header.find("Upgrade"); 498 | if(it != session->request->header.end()) { 499 | // remove connection from connections 500 | { 501 | std::unique_lock lock(*connections_mutex); 502 | auto it = connections->find(session->connection.get()); 503 | if(it != connections->end()) 504 | connections->erase(it); 505 | } 506 | 507 | on_upgrade(session->connection->socket, session->request); 508 | return; 509 | } 510 | } 511 | // Find path- and method-match, and call write_response 512 | for(auto ®ex_method : resource) { 513 | auto it = regex_method.second.find(session->request->method); 514 | if(it != regex_method.second.end()) { 515 | regex::smatch sm_res; 516 | if(regex::regex_match(session->request->path, sm_res, regex_method.first)) { 517 | session->request->path_match = std::move(sm_res); 518 | write_response(session, it->second); 519 | return; 520 | } 521 | } 522 | } 523 | auto it = default_resource.find(session->request->method); 524 | if(it != default_resource.end()) 525 | write_response(session, it->second); 526 | } 527 | 528 | void write_response(const std::shared_ptr &session, 529 | std::function::Response>, std::shared_ptr::Request>)> &resource_function) { 530 | session->connection->set_timeout(config.timeout_content); 531 | auto response = std::shared_ptr(new Response(session, config.timeout_content), [this](Response *response_ptr) { 532 | auto response = std::shared_ptr(response_ptr); 533 | response->send([this, response](const error_code &ec) { 534 | if(!ec) { 535 | if(response->close_connection_after_response) 536 | return; 537 | 538 | auto range = response->session->request->header.equal_range("Connection"); 539 | for(auto it = range.first; it != range.second; it++) { 540 | if(case_insensitive_equal(it->second, "close")) 541 | return; 542 | else if(case_insensitive_equal(it->second, "keep-alive")) { 543 | auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection); 544 | this->read_request_and_content(new_session); 545 | return; 546 | } 547 | } 548 | if(response->session->request->http_version >= "1.1") { 549 | auto new_session = std::make_shared(this->config.max_request_streambuf_size, response->session->connection); 550 | this->read_request_and_content(new_session); 551 | return; 552 | } 553 | } 554 | else if(this->on_error) 555 | this->on_error(response->session->request, ec); 556 | }); 557 | }); 558 | 559 | try { 560 | resource_function(response, session->request); 561 | } 562 | catch(const std::exception &) { 563 | if(on_error) 564 | on_error(session->request, make_error_code::make_error_code(errc::operation_canceled)); 565 | return; 566 | } 567 | } 568 | }; 569 | 570 | template 571 | class Server : public ServerBase {}; 572 | 573 | using HTTP = asio::ip::tcp::socket; 574 | 575 | template <> 576 | class Server : public ServerBase { 577 | public: 578 | Server() noexcept : ServerBase::ServerBase(80) {} 579 | 580 | protected: 581 | void accept() override { 582 | auto session = std::make_shared(config.max_request_streambuf_size, create_connection(*io_service)); 583 | 584 | acceptor->async_accept(*session->connection->socket, [this, session](const error_code &ec) { 585 | auto lock = session->connection->handler_runner->continue_lock(); 586 | if(!lock) 587 | return; 588 | 589 | // Immediately start accepting a new connection (unless io_service has been stopped) 590 | if(ec != asio::error::operation_aborted) 591 | this->accept(); 592 | 593 | if(!ec) { 594 | asio::ip::tcp::no_delay option(true); 595 | error_code ec; 596 | session->connection->socket->set_option(option, ec); 597 | 598 | this->read_request_and_content(session); 599 | } 600 | else if(this->on_error) 601 | this->on_error(session->request, ec); 602 | }); 603 | } 604 | }; 605 | } // namespace SimpleWeb 606 | 607 | #endif /* SERVER_HTTP_HPP */ 608 | -------------------------------------------------------------------------------- /status_code.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_WEB_STATUS_CODE_HPP 2 | #define SIMPLE_WEB_STATUS_CODE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace SimpleWeb { 8 | enum class StatusCode { 9 | unknown = 0, 10 | information_continue = 100, 11 | information_switching_protocols, 12 | information_processing, 13 | success_ok = 200, 14 | success_created, 15 | success_accepted, 16 | success_non_authoritative_information, 17 | success_no_content, 18 | success_reset_content, 19 | success_partial_content, 20 | success_multi_status, 21 | success_already_reported, 22 | success_im_used = 226, 23 | redirection_multiple_choices = 300, 24 | redirection_moved_permanently, 25 | redirection_found, 26 | redirection_see_other, 27 | redirection_not_modified, 28 | redirection_use_proxy, 29 | redirection_switch_proxy, 30 | redirection_temporary_redirect, 31 | redirection_permanent_redirect, 32 | client_error_bad_request = 400, 33 | client_error_unauthorized, 34 | client_error_payment_required, 35 | client_error_forbidden, 36 | client_error_not_found, 37 | client_error_method_not_allowed, 38 | client_error_not_acceptable, 39 | client_error_proxy_authentication_required, 40 | client_error_request_timeout, 41 | client_error_conflict, 42 | client_error_gone, 43 | client_error_length_required, 44 | client_error_precondition_failed, 45 | client_error_payload_too_large, 46 | client_error_uri_too_long, 47 | client_error_unsupported_media_type, 48 | client_error_range_not_satisfiable, 49 | client_error_expectation_failed, 50 | client_error_im_a_teapot, 51 | client_error_misdirection_required = 421, 52 | client_error_unprocessable_entity, 53 | client_error_locked, 54 | client_error_failed_dependency, 55 | client_error_upgrade_required = 426, 56 | client_error_precondition_required = 428, 57 | client_error_too_many_requests, 58 | client_error_request_header_fields_too_large = 431, 59 | client_error_unavailable_for_legal_reasons = 451, 60 | server_error_internal_server_error = 500, 61 | server_error_not_implemented, 62 | server_error_bad_gateway, 63 | server_error_service_unavailable, 64 | server_error_gateway_timeout, 65 | server_error_http_version_not_supported, 66 | server_error_variant_also_negotiates, 67 | server_error_insufficient_storage, 68 | server_error_loop_detected, 69 | server_error_not_extended = 510, 70 | server_error_network_authentication_required 71 | }; 72 | 73 | const static std::vector> &status_codes() noexcept { 74 | const static std::vector> status_codes = { 75 | {StatusCode::unknown, ""}, 76 | {StatusCode::information_continue, "100 Continue"}, 77 | {StatusCode::information_switching_protocols, "101 Switching Protocols"}, 78 | {StatusCode::information_processing, "102 Processing"}, 79 | {StatusCode::success_ok, "200 OK"}, 80 | {StatusCode::success_created, "201 Created"}, 81 | {StatusCode::success_accepted, "202 Accepted"}, 82 | {StatusCode::success_non_authoritative_information, "203 Non-Authoritative Information"}, 83 | {StatusCode::success_no_content, "204 No Content"}, 84 | {StatusCode::success_reset_content, "205 Reset Content"}, 85 | {StatusCode::success_partial_content, "206 Partial Content"}, 86 | {StatusCode::success_multi_status, "207 Multi-Status"}, 87 | {StatusCode::success_already_reported, "208 Already Reported"}, 88 | {StatusCode::success_im_used, "226 IM Used"}, 89 | {StatusCode::redirection_multiple_choices, "300 Multiple Choices"}, 90 | {StatusCode::redirection_moved_permanently, "301 Moved Permanently"}, 91 | {StatusCode::redirection_found, "302 Found"}, 92 | {StatusCode::redirection_see_other, "303 See Other"}, 93 | {StatusCode::redirection_not_modified, "304 Not Modified"}, 94 | {StatusCode::redirection_use_proxy, "305 Use Proxy"}, 95 | {StatusCode::redirection_switch_proxy, "306 Switch Proxy"}, 96 | {StatusCode::redirection_temporary_redirect, "307 Temporary Redirect"}, 97 | {StatusCode::redirection_permanent_redirect, "308 Permanent Redirect"}, 98 | {StatusCode::client_error_bad_request, "400 Bad Request"}, 99 | {StatusCode::client_error_unauthorized, "401 Unauthorized"}, 100 | {StatusCode::client_error_payment_required, "402 Payment Required"}, 101 | {StatusCode::client_error_forbidden, "403 Forbidden"}, 102 | {StatusCode::client_error_not_found, "404 Not Found"}, 103 | {StatusCode::client_error_method_not_allowed, "405 Method Not Allowed"}, 104 | {StatusCode::client_error_not_acceptable, "406 Not Acceptable"}, 105 | {StatusCode::client_error_proxy_authentication_required, "407 Proxy Authentication Required"}, 106 | {StatusCode::client_error_request_timeout, "408 Request Timeout"}, 107 | {StatusCode::client_error_conflict, "409 Conflict"}, 108 | {StatusCode::client_error_gone, "410 Gone"}, 109 | {StatusCode::client_error_length_required, "411 Length Required"}, 110 | {StatusCode::client_error_precondition_failed, "412 Precondition Failed"}, 111 | {StatusCode::client_error_payload_too_large, "413 Payload Too Large"}, 112 | {StatusCode::client_error_uri_too_long, "414 URI Too Long"}, 113 | {StatusCode::client_error_unsupported_media_type, "415 Unsupported Media Type"}, 114 | {StatusCode::client_error_range_not_satisfiable, "416 Range Not Satisfiable"}, 115 | {StatusCode::client_error_expectation_failed, "417 Expectation Failed"}, 116 | {StatusCode::client_error_im_a_teapot, "418 I'm a teapot"}, 117 | {StatusCode::client_error_misdirection_required, "421 Misdirected Request"}, 118 | {StatusCode::client_error_unprocessable_entity, "422 Unprocessable Entity"}, 119 | {StatusCode::client_error_locked, "423 Locked"}, 120 | {StatusCode::client_error_failed_dependency, "424 Failed Dependency"}, 121 | {StatusCode::client_error_upgrade_required, "426 Upgrade Required"}, 122 | {StatusCode::client_error_precondition_required, "428 Precondition Required"}, 123 | {StatusCode::client_error_too_many_requests, "429 Too Many Requests"}, 124 | {StatusCode::client_error_request_header_fields_too_large, "431 Request Header Fields Too Large"}, 125 | {StatusCode::client_error_unavailable_for_legal_reasons, "451 Unavailable For Legal Reasons"}, 126 | {StatusCode::server_error_internal_server_error, "500 Internal Server Error"}, 127 | {StatusCode::server_error_not_implemented, "501 Not Implemented"}, 128 | {StatusCode::server_error_bad_gateway, "502 Bad Gateway"}, 129 | {StatusCode::server_error_service_unavailable, "503 Service Unavailable"}, 130 | {StatusCode::server_error_gateway_timeout, "504 Gateway Timeout"}, 131 | {StatusCode::server_error_http_version_not_supported, "505 HTTP Version Not Supported"}, 132 | {StatusCode::server_error_variant_also_negotiates, "506 Variant Also Negotiates"}, 133 | {StatusCode::server_error_insufficient_storage, "507 Insufficient Storage"}, 134 | {StatusCode::server_error_loop_detected, "508 Loop Detected"}, 135 | {StatusCode::server_error_not_extended, "510 Not Extended"}, 136 | {StatusCode::server_error_network_authentication_required, "511 Network Authentication Required"}}; 137 | return status_codes; 138 | } 139 | 140 | inline StatusCode status_code(const std::string &status_code_str) noexcept { 141 | for(auto &status_code : status_codes()) { 142 | if(status_code.second == status_code_str) 143 | return status_code.first; 144 | } 145 | return StatusCode::unknown; 146 | } 147 | 148 | inline const std::string &status_code(StatusCode status_code_enum) noexcept { 149 | for(auto &status_code : status_codes()) { 150 | if(status_code.first == status_code_enum) 151 | return status_code.second; 152 | } 153 | return status_codes()[0].second; 154 | } 155 | } // namespace SimpleWeb 156 | 157 | #endif // SIMPLE_WEB_STATUS_CODE_HPP 158 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | g++ -std=c++14 -stdlib=libc++ -lssl -lcrypto -Wall -lboost_system main.cpp 3 | ./a.out -------------------------------------------------------------------------------- /utility.hpp: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_WEB_UTILITY_HPP 2 | #define SIMPLE_WEB_UTILITY_HPP 3 | 4 | #include "status_code.hpp" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace SimpleWeb { 12 | inline bool case_insensitive_equal(const std::string &str1, const std::string &str2) noexcept { 13 | return str1.size() == str2.size() && 14 | std::equal(str1.begin(), str1.end(), str2.begin(), [](char a, char b) { 15 | return tolower(a) == tolower(b); 16 | }); 17 | } 18 | class CaseInsensitiveEqual { 19 | public: 20 | bool operator()(const std::string &str1, const std::string &str2) const noexcept { 21 | return case_insensitive_equal(str1, str2); 22 | } 23 | }; 24 | // Based on https://stackoverflow.com/questions/2590677/how-do-i-combine-hash-values-in-c0x/2595226#2595226 25 | class CaseInsensitiveHash { 26 | public: 27 | std::size_t operator()(const std::string &str) const noexcept { 28 | std::size_t h = 0; 29 | std::hash hash; 30 | for(auto c : str) 31 | h ^= hash(tolower(c)) + 0x9e3779b9 + (h << 6) + (h >> 2); 32 | return h; 33 | } 34 | }; 35 | 36 | using CaseInsensitiveMultimap = std::unordered_multimap; 37 | 38 | /// Percent encoding and decoding 39 | class Percent { 40 | public: 41 | /// Returns percent-encoded string 42 | static std::string encode(const std::string &value) noexcept { 43 | static auto hex_chars = "0123456789ABCDEF"; 44 | 45 | std::string result; 46 | result.reserve(value.size()); // Minimum size of result 47 | 48 | for(auto &chr : value) { 49 | if(chr == ' ') 50 | result += '+'; 51 | else if(chr == '!' || chr == '#' || chr == '$' || (chr >= '&' && chr <= ',') || (chr >= '/' && chr <= ';') || chr == '=' || chr == '?' || chr == '@' || chr == '[' || chr == ']') 52 | result += std::string("%") + hex_chars[chr >> 4] + hex_chars[chr & 15]; 53 | else 54 | result += chr; 55 | } 56 | 57 | return result; 58 | } 59 | 60 | /// Returns percent-decoded string 61 | static std::string decode(const std::string &value) noexcept { 62 | std::string result; 63 | result.reserve(value.size() / 3 + (value.size() % 3)); // Minimum size of result 64 | 65 | for(std::size_t i = 0; i < value.size(); ++i) { 66 | auto &chr = value[i]; 67 | if(chr == '%' && i + 2 < value.size()) { 68 | auto hex = value.substr(i + 1, 2); 69 | auto decoded_chr = static_cast(std::strtol(hex.c_str(), nullptr, 16)); 70 | result += decoded_chr; 71 | i += 2; 72 | } 73 | else if(chr == '+') 74 | result += ' '; 75 | else 76 | result += chr; 77 | } 78 | 79 | return result; 80 | } 81 | }; 82 | 83 | /// Query string creation and parsing 84 | class QueryString { 85 | public: 86 | /// Returns query string created from given field names and values 87 | static std::string create(const CaseInsensitiveMultimap &fields) noexcept { 88 | std::string result; 89 | 90 | bool first = true; 91 | for(auto &field : fields) { 92 | result += (!first ? "&" : "") + field.first + '=' + Percent::encode(field.second); 93 | first = false; 94 | } 95 | 96 | return result; 97 | } 98 | 99 | /// Returns query keys with percent-decoded values. 100 | static CaseInsensitiveMultimap parse(const std::string &query_string) noexcept { 101 | CaseInsensitiveMultimap result; 102 | 103 | if(query_string.empty()) 104 | return result; 105 | 106 | std::size_t name_pos = 0; 107 | auto name_end_pos = std::string::npos; 108 | auto value_pos = std::string::npos; 109 | for(std::size_t c = 0; c < query_string.size(); ++c) { 110 | if(query_string[c] == '&') { 111 | auto name = query_string.substr(name_pos, (name_end_pos == std::string::npos ? c : name_end_pos) - name_pos); 112 | if(!name.empty()) { 113 | auto value = value_pos == std::string::npos ? std::string() : query_string.substr(value_pos, c - value_pos); 114 | result.emplace(std::move(name), Percent::decode(value)); 115 | } 116 | name_pos = c + 1; 117 | name_end_pos = std::string::npos; 118 | value_pos = std::string::npos; 119 | } 120 | else if(query_string[c] == '=') { 121 | name_end_pos = c; 122 | value_pos = c + 1; 123 | } 124 | } 125 | if(name_pos < query_string.size()) { 126 | auto name = query_string.substr(name_pos, name_end_pos - name_pos); 127 | if(!name.empty()) { 128 | auto value = value_pos >= query_string.size() ? std::string() : query_string.substr(value_pos); 129 | result.emplace(std::move(name), Percent::decode(value)); 130 | } 131 | } 132 | 133 | return result; 134 | } 135 | }; 136 | 137 | class HttpHeader { 138 | public: 139 | /// Parse header fields 140 | static CaseInsensitiveMultimap parse(std::istream &stream) noexcept { 141 | CaseInsensitiveMultimap result; 142 | std::string line; 143 | getline(stream, line); 144 | std::size_t param_end; 145 | while((param_end = line.find(':')) != std::string::npos) { 146 | std::size_t value_start = param_end + 1; 147 | if(value_start < line.size()) { 148 | if(line[value_start] == ' ') 149 | value_start++; 150 | if(value_start < line.size()) 151 | result.emplace(line.substr(0, param_end), line.substr(value_start, line.size() - value_start - 1)); 152 | } 153 | 154 | getline(stream, line); 155 | } 156 | return result; 157 | } 158 | }; 159 | 160 | class RequestMessage { 161 | public: 162 | /// Parse request line and header fields 163 | static bool parse(std::istream &stream, std::string &method, std::string &path, std::string &query_string, std::string &version, CaseInsensitiveMultimap &header) noexcept { 164 | header.clear(); 165 | std::string line; 166 | getline(stream, line); 167 | std::size_t method_end; 168 | if((method_end = line.find(' ')) != std::string::npos) { 169 | method = line.substr(0, method_end); 170 | 171 | std::size_t query_start = std::string::npos; 172 | std::size_t path_and_query_string_end = std::string::npos; 173 | for(std::size_t i = method_end + 1; i < line.size(); ++i) { 174 | if(line[i] == '?' && (i + 1) < line.size()) 175 | query_start = i + 1; 176 | else if(line[i] == ' ') { 177 | path_and_query_string_end = i; 178 | break; 179 | } 180 | } 181 | if(path_and_query_string_end != std::string::npos) { 182 | if(query_start != std::string::npos) { 183 | path = line.substr(method_end + 1, query_start - method_end - 2); 184 | query_string = line.substr(query_start, path_and_query_string_end - query_start); 185 | } 186 | else 187 | path = line.substr(method_end + 1, path_and_query_string_end - method_end - 1); 188 | 189 | std::size_t protocol_end; 190 | if((protocol_end = line.find('/', path_and_query_string_end + 1)) != std::string::npos) { 191 | if(line.compare(path_and_query_string_end + 1, protocol_end - path_and_query_string_end - 1, "HTTP") != 0) 192 | return false; 193 | version = line.substr(protocol_end + 1, line.size() - protocol_end - 2); 194 | } 195 | else 196 | return false; 197 | 198 | header = HttpHeader::parse(stream); 199 | } 200 | else 201 | return false; 202 | } 203 | else 204 | return false; 205 | return true; 206 | } 207 | }; 208 | 209 | class ResponseMessage { 210 | public: 211 | /// Parse status line and header fields 212 | static bool parse(std::istream &stream, std::string &version, std::string &status_code, CaseInsensitiveMultimap &header) noexcept { 213 | header.clear(); 214 | std::string line; 215 | getline(stream, line); 216 | std::size_t version_end = line.find(' '); 217 | if(version_end != std::string::npos) { 218 | if(5 < line.size()) 219 | version = line.substr(5, version_end - 5); 220 | else 221 | return false; 222 | if((version_end + 1) < line.size()) 223 | status_code = line.substr(version_end + 1, line.size() - (version_end + 1) - 1); 224 | else 225 | return false; 226 | 227 | header = HttpHeader::parse(stream); 228 | } 229 | else 230 | return false; 231 | return true; 232 | } 233 | }; 234 | 235 | class ContentDisposition { 236 | public: 237 | /// Can be used to parse the Content-Disposition header field value when 238 | /// clients are posting requests with enctype="multipart/form-data" 239 | static CaseInsensitiveMultimap parse(const std::string &line) { 240 | CaseInsensitiveMultimap result; 241 | 242 | std::size_t para_start_pos = 0; 243 | std::size_t para_end_pos = std::string::npos; 244 | std::size_t value_start_pos = std::string::npos; 245 | for(std::size_t c = 0; c < line.size(); ++c) { 246 | if(para_start_pos != std::string::npos) { 247 | if(para_end_pos == std::string::npos) { 248 | if(line[c] == ';') { 249 | result.emplace(line.substr(para_start_pos, c - para_start_pos), std::string()); 250 | para_start_pos = std::string::npos; 251 | } 252 | else if(line[c] == '=') 253 | para_end_pos = c; 254 | } 255 | else { 256 | if(value_start_pos == std::string::npos) { 257 | if(line[c] == '"' && c + 1 < line.size()) 258 | value_start_pos = c + 1; 259 | } 260 | else if(line[c] == '"') { 261 | result.emplace(line.substr(para_start_pos, para_end_pos - para_start_pos), line.substr(value_start_pos, c - value_start_pos)); 262 | para_start_pos = std::string::npos; 263 | para_end_pos = std::string::npos; 264 | value_start_pos = std::string::npos; 265 | } 266 | } 267 | } 268 | else if(line[c] != ' ' && line[c] != ';') 269 | para_start_pos = c; 270 | } 271 | if(para_start_pos != std::string::npos && para_end_pos == std::string::npos) 272 | result.emplace(line.substr(para_start_pos), std::string()); 273 | 274 | return result; 275 | } 276 | }; 277 | } // namespace SimpleWeb 278 | 279 | #ifdef __SSE2__ 280 | #include 281 | namespace SimpleWeb { 282 | inline void spin_loop_pause() noexcept { _mm_pause(); } 283 | } // namespace SimpleWeb 284 | // TODO: need verification that the following checks are correct: 285 | #elif defined(_MSC_VER) && _MSC_VER >= 1800 && (defined(_M_X64) || defined(_M_IX86)) 286 | #include 287 | namespace SimpleWeb { 288 | inline void spin_loop_pause() noexcept { _mm_pause(); } 289 | } // namespace SimpleWeb 290 | #else 291 | namespace SimpleWeb { 292 | inline void spin_loop_pause() noexcept {} 293 | } // namespace SimpleWeb 294 | #endif 295 | 296 | namespace SimpleWeb { 297 | /// Makes it possible to for instance cancel Asio handlers without stopping asio::io_service 298 | class ScopeRunner { 299 | /// Scope count that is set to -1 if scopes are to be canceled 300 | std::atomic count; 301 | 302 | public: 303 | class SharedLock { 304 | friend class ScopeRunner; 305 | std::atomic &count; 306 | SharedLock(std::atomic &count) noexcept : count(count) {} 307 | SharedLock &operator=(const SharedLock &) = delete; 308 | SharedLock(const SharedLock &) = delete; 309 | 310 | public: 311 | ~SharedLock() noexcept { 312 | count.fetch_sub(1); 313 | } 314 | }; 315 | 316 | ScopeRunner() noexcept : count(0) {} 317 | 318 | /// Returns nullptr if scope should be exited, or a shared lock otherwise 319 | std::unique_ptr continue_lock() noexcept { 320 | long expected = count; 321 | while(expected >= 0 && !count.compare_exchange_weak(expected, expected + 1)) 322 | spin_loop_pause(); 323 | 324 | if(expected < 0) 325 | return nullptr; 326 | else 327 | return std::unique_ptr(new SharedLock(count)); 328 | } 329 | 330 | /// Blocks until all shared locks are released, then prevents future shared locks 331 | void stop() noexcept { 332 | long expected = 0; 333 | while(!count.compare_exchange_weak(expected, -1)) { 334 | if(expected < 0) 335 | return; 336 | expected = 0; 337 | spin_loop_pause(); 338 | } 339 | } 340 | }; 341 | } // namespace SimpleWeb 342 | 343 | #endif // SIMPLE_WEB_UTILITY_HPP 344 | --------------------------------------------------------------------------------