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
39namespace 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 */
53WebServer::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 */
76WebServer::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 */
98WebServer::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 */
112WebServer &
113WebServer::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 */
129WebServer &
130WebServer::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 */
139void
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 */
224std::string
225WebServer::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 */
264WebServer &
265WebServer::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 */
275WebServer &
276WebServer::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 */
287WebServer &
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 */
298unsigned 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 */
307Time
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 */
321void
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.