Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / ot-br-posix / repo / third_party / Simple-web-server / repo / server_http.hpp
1 #ifndef SERVER_HTTP_HPP
2 #define SERVER_HTTP_HPP
3
4 #include <boost/asio.hpp>
5 #include <boost/algorithm/string/predicate.hpp>
6 #include <boost/functional/hash.hpp>
7
8 #include <map>
9 #include <unordered_map>
10 #include <thread>
11 #include <functional>
12 #include <iostream>
13 #include <sstream>
14
15 #ifndef CASE_INSENSITIVE_EQUALS_AND_HASH
16 #define CASE_INSENSITIVE_EQUALS_AND_HASH
17 //Based on http://www.boost.org/doc/libs/1_60_0/doc/html/unordered/hash_equality.html
18 class case_insensitive_equals {
19 public:
20   bool operator()(const std::string &key1, const std::string &key2) const {
21     return boost::algorithm::iequals(key1, key2);
22   }
23 };
24 class case_insensitive_hash {
25 public:
26   size_t operator()(const std::string &key) const {
27     std::size_t seed=0;
28     for(auto &c: key)
29       boost::hash_combine(seed, std::tolower(c));
30     return seed;
31   }
32 };
33 #endif
34
35 // Late 2017 TODO: remove the following checks and always use std::regex
36 #ifdef USE_BOOST_REGEX
37 #include <boost/regex.hpp>
38 #define REGEX_NS boost
39 #else
40 #include <regex>
41 #define REGEX_NS std
42 #endif
43
44 // TODO when switching to c++14, use [[deprecated]] instead
45 #ifndef DEPRECATED
46 #ifdef __GNUC__
47 #define DEPRECATED __attribute__((deprecated))
48 #elif defined(_MSC_VER)
49 #define DEPRECATED __declspec(deprecated)
50 #else
51 #define DEPRECATED
52 #endif
53 #endif
54
55 namespace SimpleWeb {
56     template <class socket_type>
57     class Server;
58     
59     template <class socket_type>
60     class ServerBase {
61     public:
62         virtual ~ServerBase() {}
63
64         class Response : public std::ostream {
65             friend class ServerBase<socket_type>;
66
67             boost::asio::streambuf streambuf;
68
69             std::shared_ptr<socket_type> socket;
70
71             Response(const std::shared_ptr<socket_type> &socket): std::ostream(&streambuf), socket(socket) {}
72
73         public:
74             size_t size() {
75                 return streambuf.size();
76             }
77
78             /// If true, force server to close the connection after the response have been sent.
79             ///
80             /// This is useful when implementing a HTTP/1.0-server sending content
81             /// without specifying the content length.
82             bool close_connection_after_response = false;
83         };
84         
85         class Content : public std::istream {
86             friend class ServerBase<socket_type>;
87         public:
88             size_t size() {
89                 return streambuf.size();
90             }
91             std::string string() {
92                 std::stringstream ss;
93                 ss << rdbuf();
94                 return ss.str();
95             }
96         private:
97             boost::asio::streambuf &streambuf;
98             Content(boost::asio::streambuf &streambuf): std::istream(&streambuf), streambuf(streambuf) {}
99         };
100         
101         class Request {
102             friend class ServerBase<socket_type>;
103             friend class Server<socket_type>;
104         public:
105             std::string method, path, http_version;
106
107             Content content;
108
109             std::unordered_multimap<std::string, std::string, case_insensitive_hash, case_insensitive_equals> header;
110
111             REGEX_NS::smatch path_match;
112             
113             std::string remote_endpoint_address;
114             unsigned short remote_endpoint_port;
115             
116         private:
117             Request(const socket_type &socket): content(streambuf) {
118                 try {
119                     remote_endpoint_address=socket.lowest_layer().remote_endpoint().address().to_string();
120                     remote_endpoint_port=socket.lowest_layer().remote_endpoint().port();
121                 }
122                 catch(...) {}
123             }
124             
125             boost::asio::streambuf streambuf;
126         };
127         
128         class Config {
129             friend class ServerBase<socket_type>;
130
131             Config(unsigned short port): port(port) {}
132         public:
133             /// Port number to use. Defaults to 80 for HTTP and 443 for HTTPS.
134             unsigned short port;
135             /// Number of threads that the server will use when start() is called. Defaults to 1 thread.
136             size_t thread_pool_size=1;
137             /// Timeout on request handling. Defaults to 5 seconds.
138             size_t timeout_request=5;
139             /// Timeout on content handling. Defaults to 300 seconds.
140             size_t timeout_content=300;
141             /// IPv4 address in dotted decimal form or IPv6 address in hexadecimal notation.
142             /// If empty, the address will be any address.
143             std::string address;
144             /// Set to false to avoid binding the socket to an address that is already in use. Defaults to true.
145             bool reuse_address=true;
146         };
147         ///Set before calling start().
148         Config config;
149         
150     private:
151         class regex_orderable : public REGEX_NS::regex {
152             std::string str;
153         public:
154             regex_orderable(const char *regex_cstr) : REGEX_NS::regex(regex_cstr), str(regex_cstr) {}
155             regex_orderable(const std::string &regex_str) : REGEX_NS::regex(regex_str), str(regex_str) {}
156             bool operator<(const regex_orderable &rhs) const {
157                 return str<rhs.str;
158             }
159         };
160     public:
161         /// Warning: do not add or remove resources after start() is called
162         std::map<regex_orderable, std::map<std::string,
163             std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> > > resource;
164         
165         std::map<std::string,
166             std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>, std::shared_ptr<typename ServerBase<socket_type>::Request>)> > default_resource;
167         
168         std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Request>, const boost::system::error_code&)> on_error;
169         
170         std::function<void(std::shared_ptr<socket_type> socket, std::shared_ptr<typename ServerBase<socket_type>::Request>)> on_upgrade;
171         
172         virtual void start() {
173             if(!io_service)
174                 io_service=std::make_shared<boost::asio::io_service>();
175
176             if(io_service->stopped())
177                 io_service->reset();
178
179             boost::asio::ip::tcp::endpoint endpoint;
180             if(config.address.size()>0)
181                 endpoint=boost::asio::ip::tcp::endpoint(boost::asio::ip::address::from_string(config.address), config.port);
182             else
183                 endpoint=boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), config.port);
184             
185             if(!acceptor)
186                 acceptor=std::unique_ptr<boost::asio::ip::tcp::acceptor>(new boost::asio::ip::tcp::acceptor(*io_service));
187             acceptor->open(endpoint.protocol());
188             acceptor->set_option(boost::asio::socket_base::reuse_address(config.reuse_address));
189             acceptor->bind(endpoint);
190             acceptor->listen();
191      
192             accept(); 
193             
194             //If thread_pool_size>1, start m_io_service.run() in (thread_pool_size-1) threads for thread-pooling
195             threads.clear();
196             for(size_t c=1;c<config.thread_pool_size;c++) {
197                 threads.emplace_back([this]() {
198                     io_service->run();
199                 });
200             }
201
202             //Main thread
203             if(config.thread_pool_size>0)
204                 io_service->run();
205
206             //Wait for the rest of the threads, if any, to finish as well
207             for(auto& t: threads) {
208                 t.join();
209             }
210         }
211         
212         void stop() {
213             acceptor->close();
214             if(config.thread_pool_size>0)
215                 io_service->stop();
216         }
217         
218         ///Use this function if you need to recursively send parts of a longer message
219         void send(const std::shared_ptr<Response> &response, const std::function<void(const boost::system::error_code&)>& callback=nullptr) const {
220             boost::asio::async_write(*response->socket, response->streambuf, [this, response, callback](const boost::system::error_code& ec, size_t /*bytes_transferred*/) {
221                 if(callback)
222                     callback(ec);
223             });
224         }
225
226         /// If you have your own boost::asio::io_service, store its pointer here before running start().
227         /// You might also want to set config.thread_pool_size to 0.
228         std::shared_ptr<boost::asio::io_service> io_service;
229     protected:
230         std::unique_ptr<boost::asio::ip::tcp::acceptor> acceptor;
231         std::vector<std::thread> threads;
232         
233         ServerBase(unsigned short port) : config(port) {}
234         
235         virtual void accept()=0;
236         
237         std::shared_ptr<boost::asio::deadline_timer> get_timeout_timer(const std::shared_ptr<socket_type> &socket, long seconds) {
238             if(seconds==0)
239                 return nullptr;
240             
241             auto timer=std::make_shared<boost::asio::deadline_timer>(*io_service);
242             timer->expires_from_now(boost::posix_time::seconds(seconds));
243             timer->async_wait([socket](const boost::system::error_code& ec){
244                 if(!ec) {
245                     boost::system::error_code ec;
246                     socket->lowest_layer().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
247                     socket->lowest_layer().close();
248                 }
249             });
250             return timer;
251         }
252         
253         void read_request_and_content(const std::shared_ptr<socket_type> &socket) {
254             //Create new streambuf (Request::streambuf) for async_read_until()
255             //shared_ptr is used to pass temporary objects to the asynchronous functions
256             std::shared_ptr<Request> request(new Request(*socket));
257
258             //Set timeout on the following boost::asio::async-read or write function
259             auto timer=this->get_timeout_timer(socket, config.timeout_request);
260                         
261             boost::asio::async_read_until(*socket, request->streambuf, "\r\n\r\n",
262                     [this, socket, request, timer](const boost::system::error_code& ec, size_t bytes_transferred) {
263                 if(timer)
264                     timer->cancel();
265                 if(!ec) {
266                     //request->streambuf.size() is not necessarily the same as bytes_transferred, from Boost-docs:
267                     //"After a successful async_read_until operation, the streambuf may contain additional data beyond the delimiter"
268                     //The chosen solution is to extract lines from the stream directly when parsing the header. What is left of the
269                     //streambuf (maybe some bytes of the content) is appended to in the async_read-function below (for retrieving content).
270                     size_t num_additional_bytes=request->streambuf.size()-bytes_transferred;
271                     
272                     if(!this->parse_request(request))
273                         return;
274                     
275                     //If content, read that as well
276                     auto it=request->header.find("Content-Length");
277                     if(it!=request->header.end()) {
278                         unsigned long long content_length;
279                         try {
280                             content_length=stoull(it->second);
281                         }
282                         catch(const std::exception &e) {
283                             if(on_error)
284                                 on_error(request, boost::system::error_code(boost::system::errc::protocol_error, boost::system::generic_category()));
285                             return;
286                         }
287                         if(content_length>num_additional_bytes) {
288                             //Set timeout on the following boost::asio::async-read or write function
289                             auto timer=this->get_timeout_timer(socket, config.timeout_content);
290                             boost::asio::async_read(*socket, request->streambuf,
291                                     boost::asio::transfer_exactly(content_length-num_additional_bytes),
292                                     [this, socket, request, timer]
293                                     (const boost::system::error_code& ec, size_t /*bytes_transferred*/) {
294                                 if(timer)
295                                     timer->cancel();
296                                 if(!ec)
297                                     this->find_resource(socket, request);
298                                 else if(on_error)
299                                     on_error(request, ec);
300                             });
301                         }
302                         else
303                             this->find_resource(socket, request);
304                     }
305                     else
306                         this->find_resource(socket, request);
307                 }
308                 else if(on_error)
309                     on_error(request, ec);
310             });
311         }
312
313         bool parse_request(const std::shared_ptr<Request> &request) const {
314             std::string line;
315             getline(request->content, line);
316             size_t method_end;
317             if((method_end=line.find(' '))!=std::string::npos) {
318                 size_t path_end;
319                 if((path_end=line.find(' ', method_end+1))!=std::string::npos) {
320                     request->method=line.substr(0, method_end);
321                     request->path=line.substr(method_end+1, path_end-method_end-1);
322
323                     size_t protocol_end;
324                     if((protocol_end=line.find('/', path_end+1))!=std::string::npos) {
325                         if(line.compare(path_end+1, protocol_end-path_end-1, "HTTP")!=0)
326                             return false;
327                         request->http_version=line.substr(protocol_end+1, line.size()-protocol_end-2);
328                     }
329                     else
330                         return false;
331
332                     getline(request->content, line);
333                     size_t param_end;
334                     while((param_end=line.find(':'))!=std::string::npos) {
335                         size_t value_start=param_end+1;
336                         if((value_start)<line.size()) {
337                             if(line[value_start]==' ')
338                                 value_start++;
339                             if(value_start<line.size())
340                                 request->header.emplace(line.substr(0, param_end), line.substr(value_start, line.size()-value_start-1));
341                         }
342     
343                         getline(request->content, line);
344                     }
345                 }
346                 else
347                     return false;
348             }
349             else
350                 return false;
351             return true;
352         }
353
354         void find_resource(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request) {
355             //Upgrade connection
356             if(on_upgrade) {
357                 auto it=request->header.find("Upgrade");
358                 if(it!=request->header.end()) {
359                     on_upgrade(socket, request);
360                     return;
361                 }
362             }
363             //Find path- and method-match, and call write_response
364             for(auto &regex_method: resource) {
365                 auto it=regex_method.second.find(request->method);
366                 if(it!=regex_method.second.end()) {
367                     REGEX_NS::smatch sm_res;
368                     if(REGEX_NS::regex_match(request->path, sm_res, regex_method.first)) {
369                         request->path_match=std::move(sm_res);
370                         write_response(socket, request, it->second);
371                         return;
372                     }
373                 }
374             }
375             auto it=default_resource.find(request->method);
376             if(it!=default_resource.end()) {
377                 write_response(socket, request, it->second);
378             }
379         }
380         
381         void write_response(const std::shared_ptr<socket_type> &socket, const std::shared_ptr<Request> &request, 
382                 std::function<void(std::shared_ptr<typename ServerBase<socket_type>::Response>,
383                                    std::shared_ptr<typename ServerBase<socket_type>::Request>)>& resource_function) {
384             //Set timeout on the following boost::asio::async-read or write function
385             auto timer=this->get_timeout_timer(socket, config.timeout_content);
386
387             auto response=std::shared_ptr<Response>(new Response(socket), [this, request, timer](Response *response_ptr) {
388                 auto response=std::shared_ptr<Response>(response_ptr);
389                 this->send(response, [this, response, request, timer](const boost::system::error_code& ec) {
390                     if(timer)
391                         timer->cancel();
392                     if(!ec) {
393                         if (response->close_connection_after_response)
394                             return;
395
396                         auto range=request->header.equal_range("Connection");
397                         for(auto it=range.first;it!=range.second;it++) {
398                             if(boost::iequals(it->second, "close")) {
399                                 return;
400                             } else if (boost::iequals(it->second, "keep-alive")) {
401                                 this->read_request_and_content(response->socket);
402                                 return;
403                             }
404                         }
405                         if(request->http_version >= "1.1")
406                             this->read_request_and_content(response->socket);
407                     }
408                     else if(on_error)
409                         on_error(request, ec);
410                 });
411             });
412
413             try {
414                 resource_function(response, request);
415             }
416             catch(const std::exception &e) {
417                 if(on_error)
418                     on_error(request, boost::system::error_code(boost::system::errc::operation_canceled, boost::system::generic_category()));
419                 return;
420             }
421         }
422     };
423     
424     template<class socket_type>
425     class Server : public ServerBase<socket_type> {};
426     
427     typedef boost::asio::ip::tcp::socket HTTP;
428     
429     template<>
430     class Server<HTTP> : public ServerBase<HTTP> {
431     public:
432         DEPRECATED Server(unsigned short port, size_t thread_pool_size=1, long timeout_request=5, long timeout_content=300) :
433                 Server() {
434             config.port=port;
435             config.thread_pool_size=thread_pool_size;
436             config.timeout_request=timeout_request;
437             config.timeout_content=timeout_content;
438         }
439         
440         Server() : ServerBase<HTTP>::ServerBase(80) {}
441         
442     protected:
443         void accept() {
444             //Create new socket for this connection
445             //Shared_ptr is used to pass temporary objects to the asynchronous functions
446             auto socket=std::make_shared<HTTP>(*io_service);
447                         
448             acceptor->async_accept(*socket, [this, socket](const boost::system::error_code& ec){
449                 //Immediately start accepting a new connection (if io_service hasn't been stopped)
450                 if (ec != boost::asio::error::operation_aborted)
451                     accept();
452                                 
453                 if(!ec) {
454                     boost::asio::ip::tcp::no_delay option(true);
455                     socket->set_option(option);
456                     
457                     this->read_request_and_content(socket);
458                 }
459                 else if(on_error)
460                     on_error(std::shared_ptr<Request>(new Request(*socket)), ec);
461             });
462         }
463     };
464 }
465 #endif  /* SERVER_HTTP_HPP */