Fawkes API  Fawkes Development Version
server.cpp
1 
2 /***************************************************************************
3  * server.cpp - Web server encapsulation around libmicrohttpd
4  *
5  * Created: Sun Aug 30 17:40:54 2009
6  * Copyright 2006-2018 Tim Niemueller [www.niemueller.de]
7  ****************************************************************************/
8 
9 /* This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU Library General Public License for more details.
18  *
19  * Read the full text in the LICENSE.GPL file in the doc directory.
20  */
21 
22 #include <core/exception.h>
23 #include <core/exceptions/system.h>
24 #include <core/threading/thread.h>
25 #include <logging/logger.h>
26 #include <sys/socket.h>
27 #include <webview/access_log.h>
28 #include <webview/request.h>
29 #include <webview/request_dispatcher.h>
30 #include <webview/request_manager.h>
31 #include <webview/server.h>
32 
33 #include <cerrno>
34 #include <cstdio>
35 #include <cstdlib>
36 #include <microhttpd.h>
37 
38 namespace fawkes {
39 
40 /** @class WebServer <webview/server.h>
41  * Encapsulation of the libmicrohttpd webserver.
42  * This class opens a port serving websites and calls the supplied dispatcher
43  * for requests.
44  * @author Tim Niemueller
45  */
46 
47 /** Constructor.
48  * @param port TCP port to listen on
49  * @param dispatcher dispatcher to call for requests
50  * @param logger optional logger, used to output possible run-time problems
51  */
52 WebServer::WebServer(unsigned short int port,
53  WebRequestDispatcher *dispatcher,
54  fawkes::Logger * logger)
55 {
56  port_ = port;
57  dispatcher_ = dispatcher;
58  logger_ = logger;
59  request_manager_ = NULL;
60 
61  enable_ipv4_ = true;
62  enable_ipv6_ = true;
63 
64  tls_enabled_ = false;
65  num_threads_ = 1;
66 }
67 
68 /** Setup Transport Layer Security (encryption),
69  * @param key_pem_filepath path to PEM formatted file containing the key
70  * @param cert_pem_filepath path to PEM formatted file containing the certificate
71  * @param cipher_suite which cipers to use for SSL/TLS connections
72  * @return *this to allow for chaining
73  */
74 WebServer &
75 WebServer::setup_tls(const char *key_pem_filepath,
76  const char *cert_pem_filepath,
77  const char *cipher_suite)
78 {
79  tls_enabled_ = true;
80  tls_key_mem_ = read_file(key_pem_filepath);
81  tls_cert_mem_ = read_file(cert_pem_filepath);
82  if (cipher_suite == NULL) {
83  tls_cipher_suite_ = WEBVIEW_DEFAULT_CIPHERS;
84  } else {
85  tls_cipher_suite_ = cipher_suite;
86  }
87 
88  return *this;
89 }
90 
91 /** Setup protocols, i.e., IPv4 and/or IPv6.
92  * @param enable_ipv4 enable IPv4 support
93  * @param enable_ipv6 enable IPv6 support
94  * @return *this to allow for chaining
95  */
96 WebServer &
97 WebServer::setup_ipv(bool enable_ipv4, bool enable_ipv6)
98 {
99  enable_ipv4_ = enable_ipv4;
100  enable_ipv6_ = enable_ipv6;
101 
102  return *this;
103 }
104 
105 /** Setup cross-origin resource sharing
106  * @param allow_all allow access to all hosts
107  * @param origins allow access from these specific origins
108  * @param max_age maximum cache time to send to the client, zero to disable
109  * @return *this to allow for chaining
110  */
111 WebServer &
112 WebServer::setup_cors(bool allow_all, std::vector<std::string> &&origins, unsigned int max_age)
113 {
114  cors_allow_all_ = allow_all;
115  cors_origins_ = std::move(origins);
116  cors_max_age_ = max_age;
117 
118  return *this;
119 }
120 
121 /** Setup thread pool.
122  * This also enables epoll on Linux.
123  * @param num_threads number of threads in thread pool. If this equals
124  * one, thread pooling will be disabled and will process requests from
125  * within webview thread.
126  * @return *this to allow for chaining
127  */
128 WebServer &
129 WebServer::setup_thread_pool(unsigned int num_threads)
130 {
131  num_threads_ = num_threads;
132 
133  return *this;
134 }
135 
136 /** Start daemon and enable processing requests.
137  */
138 void
140 {
141  unsigned int flags = MHD_NO_FLAG;
142 #if MHD_VERSION >= 0x00090280
143  if (enable_ipv4_ && enable_ipv6_) {
144  flags |= MHD_USE_DUAL_STACK;
145  } else if (enable_ipv6_) {
146  flags |= MHD_USE_IPv6;
147  } else if (!enable_ipv4_ && !enable_ipv6_) {
148  throw fawkes::Exception("WebServer: neither IPv4 nor IPv6 enabled");
149  }
150 #endif
151 
152  if (tls_enabled_) {
153  flags |= MHD_USE_SSL;
154  }
155 
156  dispatcher_->setup_cors(cors_allow_all_, std::move(cors_origins_), cors_max_age_);
157 
158  if (num_threads_ > 1) {
159 #ifdef __linux__
160  flags |= MHD_USE_EPOLL_LINUX_ONLY;
161 #endif
162  flags |= MHD_USE_SELECT_INTERNALLY;
163  }
164 
165  size_t num_options = 3 + (num_threads_ > 1 ? 1 : 0) + (tls_enabled_ ? 3 : 0);
166 
167  size_t cur_op = 0;
168  struct MHD_OptionItem ops[num_options];
169  ops[cur_op++] = MHD_OptionItem{MHD_OPTION_NOTIFY_COMPLETED,
171  (void *)dispatcher_};
172  ops[cur_op++] = MHD_OptionItem{MHD_OPTION_URI_LOG_CALLBACK,
174  (void *)dispatcher_};
175 
176  if (num_threads_ > 1) {
177  ops[cur_op++] = MHD_OptionItem{MHD_OPTION_THREAD_POOL_SIZE, num_threads_, NULL};
178  }
179 
180  if (tls_enabled_) {
181  ops[cur_op++] = MHD_OptionItem{MHD_OPTION_HTTPS_MEM_KEY, (intptr_t)tls_key_mem_.c_str(), NULL};
182  ops[cur_op++] =
183  MHD_OptionItem{MHD_OPTION_HTTPS_MEM_CERT, (intptr_t)tls_cert_mem_.c_str(), NULL};
184  ops[cur_op++] =
185  MHD_OptionItem{MHD_OPTION_HTTPS_PRIORITIES, (intptr_t)tls_cipher_suite_.c_str(), NULL};
186  }
187 
188  ops[cur_op++] = MHD_OptionItem{MHD_OPTION_END, 0, NULL};
189 
190  daemon_ = MHD_start_daemon(flags,
191  port_,
192  NULL,
193  NULL,
195  (void *)dispatcher_,
196  MHD_OPTION_ARRAY,
197  ops,
198  MHD_OPTION_END);
199 
200  if (daemon_ == NULL) {
201  throw fawkes::Exception("Could not start microhttpd");
202  }
203 }
204 
205 /** Destructor. */
207 {
208  if (request_manager_) {
209  request_manager_->set_server(NULL);
210  }
211 
212  MHD_stop_daemon(daemon_);
213  daemon_ = NULL;
214  dispatcher_ = NULL;
215 }
216 
217 /** Read file into memory.
218  * @param filename file path
219  * @return string with file content.
220  * Note that this expects reasonably small file sizes that can be held
221  * in memory completely, as is the case for TLS certificates.
222  */
223 std::string
224 WebServer::read_file(const char *filename)
225 {
226  FILE *f = fopen(filename, "rb");
227  if (!f) {
228  throw CouldNotOpenFileException(filename, errno);
229  }
230 
231  long size = 0;
232  if ((fseek(f, 0, SEEK_END) != 0) || ((size = ftell(f)) == 1)) {
233  fclose(f);
234  throw Exception("Cannot determine file size of %s", filename);
235  }
236  fseek(f, 0, SEEK_SET);
237 
238  if (size == 0) {
239  fclose(f);
240  throw Exception("File %s has zero length", filename);
241  } else if (size > 1024 * 1024) {
242  // keys or certs should not be that long...
243  fclose(f);
244  throw Exception("File %s is unexpectedly large", filename);
245  }
246 
247  std::string rv(size + 1, 0);
248  if (fread(&rv[0], size, 1, f) != 1) {
249  int terrno = errno;
250  fclose(f);
251  throw FileReadException(filename, terrno);
252  }
253  fclose(f);
254 
255  return rv;
256 }
257 
258 /** Setup basic authentication.
259  * @param realm authentication realm to display to the user
260  * @param verifier verifier to use for checking credentials
261  * @return *this to allow for chaining
262  */
263 WebServer &
264 WebServer::setup_basic_auth(const char *realm, WebUserVerifier *verifier)
265 {
266  dispatcher_->setup_basic_auth(realm, verifier);
267  return *this;
268 }
269 
270 /** Setup access log.
271  * @param filename access log file name
272  * @return *this to allow for chaining
273  */
274 WebServer &
275 WebServer::setup_access_log(const char *filename)
276 {
277  dispatcher_->setup_access_log(filename);
278  return *this;
279 }
280 
281 /** Setup this server as request manager.
282  * The registration will be cancelled automatically on destruction.
283  * @param request_manager request manager to register with
284  * @return *this to allow for chaining
285  */
286 WebServer &
288 {
289  request_manager->set_server(this);
290  request_manager_ = request_manager;
291  return *this;
292 }
293 
294 /** Get number of active requests.
295  * @return number of ongoing requests.
296  */
297 unsigned int
299 {
300  return dispatcher_->active_requests();
301 }
302 
303 /** Get time when last request was completed.
304  * @return Time when last request was completed
305  */
306 Time
308 {
309  return dispatcher_->last_request_completion_time();
310 }
311 
312 /** Process requests.
313  * This method waits for new requests and processes them when
314  * received. It is necessary to call this function if running the
315  * server in single thread mode, i.e., setup_thread_pool() has not
316  * been called or only for a single thread. The function may always
317  * be called safely, even in thread pool mode. However, when called in
318  * thread pool mode, the function will always return immediately.
319  */
320 void
322 {
323  if (num_threads_ > 1) {
324  // nothing to be done when using thread pool mode
325  return;
326  }
327 
328  fd_set read_fd, write_fd, except_fd;
329  int max_fd = 0;
330  FD_ZERO(&read_fd);
331  FD_ZERO(&write_fd);
332  FD_ZERO(&except_fd);
333  if (MHD_get_fdset(daemon_, &read_fd, &write_fd, &except_fd, &max_fd) != MHD_YES) {
334  if (logger_)
335  logger_->log_warn("WebviewThread", "Could not get microhttpd fdsets");
336  return;
337  }
338  select(max_fd + 1, &read_fd, &write_fd, &except_fd, NULL);
339  Thread::CancelState old_state;
341  MHD_run(daemon_);
342  Thread::set_cancel_state(old_state);
343 }
344 
345 } // end namespace fawkes
fawkes::WebServer::active_requests
unsigned int active_requests() const
Get number of active requests.
Definition: server.cpp:303
fawkes::WebServer::~WebServer
~WebServer()
Destructor.
Definition: server.cpp:211
fawkes::WebRequestDispatcher::process_request_cb
static int process_request_cb(void *callback_data, struct MHD_Connection *connection, const char *url, const char *method, const char *version, const char *upload_data, size_t *upload_data_size, void **session_data)
Process request callback for libmicrohttpd.
Definition: request_dispatcher.cpp:166
fawkes::WebRequestDispatcher::setup_cors
void setup_cors(bool allow_all, std::vector< std::string > &&origins, unsigned int max_age)
Setup cross-origin resource sharing.
Definition: request_dispatcher.cpp:133
fawkes::WebServer::WebServer
WebServer(unsigned short int port, WebRequestDispatcher *dispatcher, fawkes::Logger *logger=0)
Constructor.
Definition: server.cpp:57
fawkes::WebServer
Definition: server.h:43
fawkes::Thread::CancelState
CancelState
Cancel state.
Definition: thread.h:64
fawkes::WebServer::last_request_completion_time
Time last_request_completion_time() const
Get time when last request was completed.
Definition: server.cpp:312
fawkes::CouldNotOpenFileException
Definition: system.h:58
fawkes::WebRequestManager
Definition: request_manager.h:40
fawkes::Logger
Definition: logger.h:41
fawkes::WebRequestDispatcher::active_requests
unsigned int active_requests() const
Get number of active requests.
Definition: request_dispatcher.cpp:551
fawkes::WebServer::setup_cors
WebServer & setup_cors(bool allow_all, std::vector< std::string > &&origins, unsigned int max_age)
Setup cross-origin resource sharing.
Definition: server.cpp:117
fawkes
fawkes::WebServer::setup_tls
WebServer & setup_tls(const char *key_pem_filepath, const char *cert_pem_filepath, const char *cipher_suite=WEBVIEW_DEFAULT_CIPHERS)
Setup Transport Layer Security (encryption),.
Definition: server.cpp:80
fawkes::Thread::CANCEL_DISABLED
@ CANCEL_DISABLED
thread cannot be cancelled
Definition: thread.h:66
fawkes::Logger::log_warn
virtual void log_warn(const char *component, const char *format,...)=0
fawkes::WebRequestDispatcher::request_completed_cb
static void request_completed_cb(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe)
Process request completion.
Definition: request_dispatcher.cpp:187
fawkes::Time
Definition: time.h:98
fawkes::WebServer::setup_basic_auth
WebServer & setup_basic_auth(const char *realm, WebUserVerifier *verifier)
Setup basic authentication.
Definition: server.cpp:269
fawkes::WebRequestDispatcher::uri_log_cb
static void * uri_log_cb(void *cls, const char *uri)
Callback for new requests.
Definition: request_dispatcher.cpp:148
fawkes::WebServer::setup_access_log
WebServer & setup_access_log(const char *filename)
Setup access log.
Definition: server.cpp:280
fawkes::WebRequestDispatcher::last_request_completion_time
Time last_request_completion_time() const
Get time when last request was completed.
Definition: request_dispatcher.cpp:561
fawkes::Thread::set_cancel_state
static void set_cancel_state(CancelState new_state, CancelState *old_state=0)
Set the cancel state of the current thread.
Definition: thread.cpp:1402
fawkes::WebServer::setup_request_manager
WebServer & setup_request_manager(WebRequestManager *request_manager)
Setup this server as request manager.
Definition: server.cpp:292
fawkes::WebServer::setup_thread_pool
WebServer & setup_thread_pool(unsigned int num_threads)
Setup thread pool.
Definition: server.cpp:134
fawkes::WebRequestDispatcher::setup_basic_auth
void setup_basic_auth(const char *realm, WebUserVerifier *verifier)
Setup basic authentication.
Definition: request_dispatcher.cpp:99
fawkes::WebRequestDispatcher::setup_access_log
void setup_access_log(const char *filename)
Setup access log.
Definition: request_dispatcher.cpp:120
fawkes::WebServer::start
void start()
Start daemon and enable processing requests.
Definition: server.cpp:144
fawkes::WebServer::process
void process()
Process requests.
Definition: server.cpp:326
fawkes::WebServer::setup_ipv
WebServer & setup_ipv(bool enable_ipv4, bool enable_ipv6)
Setup protocols, i.e., IPv4 and/or IPv6.
Definition: server.cpp:102
fawkes::Exception
Definition: exception.h:41