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