Преглед изворни кода

Draft of a new client API and server start API

The new experimental functions call the existing ones, without implementing
all new possible options yet.
bel2125 пре 5 година
родитељ
комит
a94551731b
2 измењених фајлова са 532 додато и 50 уклоњено
  1. 48 3
      include/civetweb.h
  2. 484 47
      src/civetweb.c

+ 48 - 3
include/civetweb.h

@@ -268,7 +268,8 @@ struct mg_callbacks {
 	    -1: initializing ssl fails.*/
 	int (*external_ssl_ctx)(void **ssl_ctx, void *user_data);
 
-#if defined(MG_LEGACY_INTERFACE) /* 2015-08-19 */
+#if defined(MG_LEGACY_INTERFACE)           /* 2015-08-19 */                    \
+    || defined(MG_EXPERIMENTAL_INTERFACES) /* 2019-11-03 */
 	/* Called when websocket request is received, before websocket handshake.
 	   Return value:
 	     0: civetweb proceeds with websocket handshake.
@@ -381,7 +382,6 @@ struct mg_callbacks {
 	                    int thread_type,
 	                    void *thread_pointer);
 
-
 	/* Called when initializing a new connection object.
 	 * Can be used to initialize the connection specific user data
 	 * (mg_request_info->conn_data, mg_get_user_connection_data).
@@ -1153,6 +1153,10 @@ CIVETWEB_API int mg_get_cookie(const char *cookie,
      struct mg_connection *conn;
      conn = mg_download("google.com", 80, 0, ebuf, sizeof(ebuf),
                         "%s", "GET / HTTP/1.0\r\nHost: google.com\r\n\r\n");
+
+   mg_download is equivalent to calling mg_connect_client followed by
+   mg_printf and mg_get_response. Using these three functions directly may
+   allow more control as compared to using mg_download.
  */
 CIVETWEB_API struct mg_connection *
 mg_download(const char *host,
@@ -1520,7 +1524,7 @@ CIVETWEB_API int
 mg_get_context_info(const struct mg_context *ctx, char *buffer, int buflen);
 
 
-#ifdef MG_EXPERIMENTAL_INTERFACES
+#if defined(MG_EXPERIMENTAL_INTERFACES)
 /* Get connection information. Useful for server diagnosis.
    Parameters:
      ctx: Context handle
@@ -1544,6 +1548,47 @@ CIVETWEB_API int mg_get_connection_info(const struct mg_context *ctx,
 #endif
 
 
+/* New APIs for enhanced option and error handling.
+   These mg_*2 API functions have the same purpose as their original versions,
+   but provide additional options and/or provide improved error diagnostics.
+
+   Note: Experimental interfaces may change
+*/
+struct mg_error_data {
+	unsigned *code;          /* error code (number) */
+	char *text;              /* buffer for error text */
+	size_t text_buffer_size; /* size of buffer of "text" */
+};
+
+struct mg_init_data {
+	const struct mg_callbacks *callbacks; /* callback function pointer */
+	void *user_data;                      /* data */
+	const char **configuration_options;
+};
+
+
+#if defined(MG_EXPERIMENTAL_INTERFACES)
+
+CIVETWEB_API struct mg_connection *
+mg_connect_client2(const char *host,
+                   const char *protocol,
+                   int port,
+                   const char *path,
+                   struct mg_init_data *init,
+                   struct mg_error_data *error);
+
+CIVETWEB_API int mg_get_response2(struct mg_connection *conn,
+                                  struct mg_error_data *error,
+                                  int timeout);
+
+CIVETWEB_API struct mg_context *mg_start2(struct mg_init_data *init,
+                                          struct mg_error_data *error);
+
+CIVETWEB_API int mg_start_domain2(struct mg_context *ctx,
+                                  const char **configuration_options,
+                                  struct mg_error_data *error);
+#endif
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */

+ 484 - 47
src/civetweb.c

@@ -13696,7 +13696,8 @@ is_in_script_path(const struct mg_connection *conn, const char *path)
 }
 
 
-#if defined(USE_WEBSOCKET) && defined(MG_LEGACY_INTERFACE)
+#if defined(USE_WEBSOCKET)                                                     \
+    && (defined(MG_LEGACY_INTERFACE) || defined(MG_EXPERIMENTAL_INTERFACES))
 static int
 deprecated_websocket_connect_wrapper(const struct mg_connection *conn,
                                      void *cbdata)
@@ -13734,6 +13735,17 @@ deprecated_websocket_data_wrapper(struct mg_connection *conn,
 	/* No handler set - assume "OK" */
 	return 1;
 }
+
+
+static void
+deprecated_websocket_close_wrapper(const struct mg_connection *conn,
+                                   void *cbdata)
+{
+	struct mg_callbacks *pcallbacks = (struct mg_callbacks *)cbdata;
+	if (pcallbacks->connection_close) {
+		pcallbacks->connection_close(conn);
+	}
+}
 #endif
 
 
@@ -14107,7 +14119,7 @@ handle_request(struct mg_connection *conn)
 			    deprecated_websocket_ready_wrapper,
 			    deprecated_websocket_data_wrapper,
 			    NULL,
-			    conn->phys_ctx->user_data);
+			    conn->phys_ctx->callbacks);
 #else
 			mg_send_http_error(conn, 404, "%s", "Not found");
 #endif
@@ -15011,14 +15023,14 @@ set_uid_option(struct mg_context *phys_ctx)
 		const char *run_as_user = phys_ctx->dd.config[RUN_AS_USER];
 		const struct passwd *to_pw = NULL;
 
-		if (run_as_user != NULL && (to_pw = getpwnam(run_as_user)) == NULL) {
+		if ((run_as_user != NULL) && (to_pw = getpwnam(run_as_user)) == NULL) {
 			/* run_as_user does not exist on the system. We can't proceed
 			 * further. */
 			mg_cry_ctx_internal(phys_ctx,
 			                    "%s: unknown user [%s]",
 			                    __func__,
 			                    run_as_user);
-		} else if (run_as_user == NULL || curr_uid == to_pw->pw_uid) {
+		} else if ((run_as_user == NULL) || (curr_uid == to_pw->pw_uid)) {
 			/* There was either no request to change user, or we're already
 			 * running as run_as_user. Nothing else to do.
 			 */
@@ -16733,6 +16745,96 @@ mg_connect_client(const char *host,
 }
 
 
+#if defined(MG_EXPERIMENTAL_INTERFACES)
+struct mg_connection *
+mg_connect_client2(const char *host,
+                   const char *protocol,
+                   int port,
+                   const char *path,
+                   struct mg_init_data *init,
+                   struct mg_error_data *error)
+{
+	int is_ssl, is_ws;
+	void *user_data = (init != NULL) ? init->user_data : NULL;
+
+	if (error != NULL) {
+		error->code = 0;
+		if (error->text_buffer_size > 0) {
+			*error->text = 0;
+		}
+	}
+
+	if ((host == NULL) || (protocol == NULL)) {
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "Invalid parameters");
+		}
+		return NULL;
+	}
+
+	/* check all known protocolls */
+	if (!mg_strcasecmp(protocol, "http")) {
+		is_ssl = 0;
+		is_ws = 0;
+	} else if (!mg_strcasecmp(protocol, "https")) {
+		is_ssl = 1;
+		is_ws = 0;
+#if defined(USE_WEBSOCKET)
+	} else if (!mg_strcasecmp(protocol, "ws")) {
+		is_ssl = 0;
+		is_ws = 1;
+	} else if (!mg_strcasecmp(protocol, "wss")) {
+		is_ssl = 1;
+		is_ws = 1;
+#endif
+	} else {
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "Protocol %s not supported",
+			            protocol);
+		}
+		return NULL;
+	}
+
+	/* TODO: The current implementation here just calls the old implementations,
+	 * without using any new options. This is just a first step to test the new
+	 * interfaces. */
+#if defined(USE_WEBSOCKET)
+	if (is_ws) {
+		/* TODO: implement all options */
+		return mg_connect_websocket_client(host,
+		                                   port,
+		                                   is_ssl,
+		                                   error->text,
+		                                   error->text_buffer_size,
+		                                   (path ? path : ""),
+		                                   NULL /* TODO: origin */,
+		                                   deprecated_websocket_data_wrapper,
+		                                   deprecated_websocket_close_wrapper,
+		                                   (void *)init->callbacks);
+	}
+#endif
+
+	/* TODO: all additional options */
+	struct mg_client_options opts;
+	memset(&opts, 0, sizeof(opts));
+	opts.host = host;
+	opts.port = port;
+	return mg_connect_client_impl(&opts,
+	                              is_ssl,
+	                              error->text,
+	                              error->text_buffer_size);
+}
+#endif
+
+
 static const struct {
 	const char *proto;
 	size_t proto_len;
@@ -18542,10 +18644,9 @@ legacy_init(const char **options)
 	}
 }
 
+
 struct mg_context *
-mg_start(const struct mg_callbacks *callbacks,
-         void *user_data,
-         const char **options)
+mg_start2(struct mg_init_data *init, struct mg_error_data *error)
 {
 	struct mg_context *ctx;
 	const char *name, *value, *default_value;
@@ -18553,6 +18654,8 @@ mg_start(const struct mg_callbacks *callbacks,
 	unsigned int i;
 	int itmp;
 	void (*exit_callback)(const struct mg_context *ctx) = 0;
+	const char **options =
+	    ((init != NULL) ? (init->configuration_options) : (NULL));
 
 	struct mg_workerTLS tls;
 
@@ -18561,6 +18664,13 @@ mg_start(const struct mg_callbacks *callbacks,
 	WSAStartup(MAKEWORD(2, 2), &data);
 #endif /* _WIN32  */
 
+	if (error != NULL) {
+		error->code = 0;
+		if (error->text_buffer_size > 0) {
+			*error->text = 0;
+		}
+	}
+
 	if (mg_init_library_called == 0) {
 		/* Legacy INIT, if mg_start is called without mg_init_library.
 		 * Note: This will cause a memory leak when unloading the library. */
@@ -18569,6 +18679,14 @@ mg_start(const struct mg_callbacks *callbacks,
 
 	/* Allocate context and initialize reasonable general case defaults. */
 	if ((ctx = (struct mg_context *)mg_calloc(1, sizeof(*ctx))) == NULL) {
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "Out of memory");
+		}
 		return NULL;
 	}
 
@@ -18576,7 +18694,7 @@ mg_start(const struct mg_callbacks *callbacks,
 	ctx->dd.auth_nonce_mask =
 	    (uint64_t)get_random() ^ (uint64_t)(ptrdiff_t)(options);
 
-	tls.is_master = -1;
+	tls.is_master = -1; /* Thread calling mg_start */
 	tls.thread_idx = (unsigned)mg_atomic_inc(&thread_idx_max);
 #if defined(_WIN32)
 	tls.pthread_cond_helper_mutex = NULL;
@@ -18590,22 +18708,36 @@ mg_start(const struct mg_callbacks *callbacks,
 #endif
 	ok &= (0 == pthread_mutex_init(&ctx->nonce_mutex, &pthread_mutex_attr));
 	if (!ok) {
+		const char *err_msg =
+		    "Cannot initialize thread synchronization objects";
 		/* Fatal error - abort start. However, this situation should never
 		 * occur in practice. */
-		mg_cry_ctx_internal(ctx,
+
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
 		                    "%s",
-		                    "Cannot initialize thread synchronization objects");
+			            err_msg);
+		}
+
 		mg_free(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
 	}
 
-	if (callbacks) {
-		ctx->callbacks = *callbacks;
-		exit_callback = callbacks->exit_context;
+	if ((init != NULL) && (init->callbacks != NULL)) {
+		/* Set all callbacks except exit_context. */
+		ctx->callbacks = *init->callbacks;
+		exit_callback = init->callbacks->exit_context;
+		/* The exit callback is activated once the context is successfully
+		 * created. It should not be called, if an incomplete context object is
+		 * deleted during a failed initialization. */
 		ctx->callbacks.exit_context = 0;
 	}
-	ctx->user_data = user_data;
+	ctx->user_data = ((init != NULL) ? (init->user_data) : (NULL));
 	ctx->dd.handlers = NULL;
 	ctx->dd.next = NULL;
 
@@ -18617,16 +18749,34 @@ mg_start(const struct mg_callbacks *callbacks,
 	while (options && (name = *options++) != NULL) {
 		if ((idx = get_option_index(name)) == -1) {
 			mg_cry_ctx_internal(ctx, "Invalid option: %s", name);
+			if ((error != NULL) && (error->text_buffer_size > 0)) {
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for error buffers */
+				            error->text,
+				            error->text_buffer_size,
+				            "Invalid configuration option: %s",
+				            name);
+			}
 			free_context(ctx);
 			pthread_setspecific(sTlsKey, NULL);
 			return NULL;
 		} else if ((value = *options++) == NULL) {
 			mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name);
+			if ((error != NULL) && (error->text_buffer_size > 0)) {
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for error buffers */
+				            error->text,
+				            error->text_buffer_size,
+				            "Invalid configuration option value: %s",
+				            name);
+			}
 			free_context(ctx);
 			pthread_setspecific(sTlsKey, NULL);
 			return NULL;
 		}
 		if (ctx->dd.config[idx] != NULL) {
+			/* A duplicate configuration option is not an error - the last
+			 * option value will be used. */
 			mg_cry_ctx_internal(ctx, "warning: %s: duplicate option", name);
 			mg_free(ctx->dd.config[idx]);
 		}
@@ -18646,6 +18796,14 @@ mg_start(const struct mg_callbacks *callbacks,
 	itmp = atoi(ctx->dd.config[MAX_REQUEST_SIZE]);
 	if (itmp < 1024) {
 		mg_cry_ctx_internal(ctx, "%s", "max_request_size too small");
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "Invalid configuration option value: %s",
+			            config_options[MAX_REQUEST_SIZE].name);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18655,15 +18813,20 @@ mg_start(const struct mg_callbacks *callbacks,
 	/* Worker thread count option */
 	workerthreadcount = atoi(ctx->dd.config[NUM_THREADS]);
 
-	if (workerthreadcount > MAX_WORKER_THREADS) {
-		mg_cry_ctx_internal(ctx, "%s", "Too many worker threads");
-		free_context(ctx);
-		pthread_setspecific(sTlsKey, NULL);
-		return NULL;
-	}
-
+	if ((workerthreadcount > MAX_WORKER_THREADS) || (workerthreadcount <= 0)) {
 	if (workerthreadcount <= 0) {
 		mg_cry_ctx_internal(ctx, "%s", "Invalid number of worker threads");
+		} else {
+			mg_cry_ctx_internal(ctx, "%s", "Too many worker threads");
+		}
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "Invalid configuration option value: %s",
+			            config_options[NUM_THREADS].name);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18673,6 +18836,14 @@ mg_start(const struct mg_callbacks *callbacks,
 #if defined(NO_FILES)
 	if (ctx->dd.config[DOCUMENT_ROOT] != NULL) {
 		mg_cry_ctx_internal(ctx, "%s", "Document root must not be set");
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "Invalid configuration option value: %s",
+			            config_options[DOCUMENT_ROOT].name);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18692,6 +18863,15 @@ mg_start(const struct mg_callbacks *callbacks,
 		    ctx->dd.config[LUA_BACKGROUND_SCRIPT], ctx, ebuf, sizeof(ebuf));
 		if (!state) {
 			mg_cry_ctx_internal(ctx, "lua_background_script error: %s", ebuf);
+			if ((error != NULL) && (error->text_buffer_size > 0)) {
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for error buffers */
+				            error->text,
+				            error->text_buffer_size,
+				            "Error in script %s: %s",
+				            config_options[DOCUMENT_ROOT].name,
+				            ebuf);
+			}
 			free_context(ctx);
 			pthread_setspecific(sTlsKey, NULL);
 			return NULL;
@@ -18716,20 +18896,99 @@ mg_start(const struct mg_callbacks *callbacks,
 	}
 #endif
 
-	/* NOTE(lsm): order is important here. SSL certificates must
-	 * be initialized before listening ports. UID must be set last. */
-	if (
+	/* Step by step initialization of ctx - depending on build options */
 #if !defined(NO_FILESYSTEMS)
-	    !set_gpass_option(ctx, NULL) ||
+	if (!set_gpass_option(ctx, NULL)) {
+		const char *err_msg = "Invalid global password file";
+		/* Fatal error - abort start. */
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
+		free_context(ctx);
+		pthread_setspecific(sTlsKey, NULL);
+		return NULL;
+	}
 #endif
+
 #if !defined(NO_SSL)
-	    !init_ssl_ctx(ctx, NULL) ||
+	if (!init_ssl_ctx(ctx, NULL)) {
+		const char *err_msg = "Error initializing SSL context";
+		/* Fatal error - abort start. */
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
+		free_context(ctx);
+		pthread_setspecific(sTlsKey, NULL);
+		return NULL;
+	}
 #endif
-	    !set_ports_option(ctx) ||
+
+	if (!set_ports_option(ctx)) {
+		const char *err_msg = "Failed to setup server ports";
+		/* Fatal error - abort start. */
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
+		free_context(ctx);
+		pthread_setspecific(sTlsKey, NULL);
+		return NULL;
+	}
+
+
 #if !defined(_WIN32) && !defined(__ZEPHYR__)
-	    !set_uid_option(ctx) ||
+	if (!set_uid_option(ctx)) {
+		const char *err_msg = "Failed to run as configured user";
+		/* Fatal error - abort start. */
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
+		free_context(ctx);
+		pthread_setspecific(sTlsKey, NULL);
+		return NULL;
+	}
 #endif
-	    !set_acl_option(ctx)) {
+
+	if (!set_acl_option(ctx)) {
+		const char *err_msg = "Failed to setup access control list";
+		/* Fatal error - abort start. */
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18741,9 +19000,17 @@ mg_start(const struct mg_callbacks *callbacks,
 	                                                   ctx);
 
 	if (ctx->worker_threadids == NULL) {
-		mg_cry_ctx_internal(ctx,
+		const char *err_msg = "Not enough memory for worker thread ID array";
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
 		                    "%s",
-		                    "Not enough memory for worker thread ID array");
+			            err_msg);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18753,24 +19020,41 @@ mg_start(const struct mg_callbacks *callbacks,
 	                                          sizeof(struct mg_connection),
 	                                          ctx);
 	if (ctx->worker_connections == NULL) {
-		mg_cry_ctx_internal(
-		    ctx, "%s", "Not enough memory for worker thread connection array");
+		const char *err_msg =
+		    "Not enough memory for worker thread connection array";
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
 	}
 
-
 #if defined(ALTERNATIVE_QUEUE)
 	ctx->client_wait_events =
 	    (void **)mg_calloc_ctx(ctx->cfg_worker_threads,
 	                           sizeof(ctx->client_wait_events[0]),
 	                           ctx);
 	if (ctx->client_wait_events == NULL) {
-		mg_cry_ctx_internal(ctx,
-		                    "%s",
-		                    "Not enough memory for worker event array");
+		const char *err_msg = "Not enough memory for worker event array";
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
 		mg_free(ctx->worker_threadids);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18781,11 +19065,19 @@ mg_start(const struct mg_callbacks *callbacks,
 	                                   sizeof(ctx->client_socks[0]),
 	                                   ctx);
 	if (ctx->client_socks == NULL) {
-		mg_cry_ctx_internal(ctx,
-		                    "%s",
-		                    "Not enough memory for worker socket array");
+		const char *err_msg = "Not enough memory for worker socket array";
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
 		mg_free(ctx->client_wait_events);
 		mg_free(ctx->worker_threadids);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18794,7 +19086,8 @@ mg_start(const struct mg_callbacks *callbacks,
 	for (i = 0; (unsigned)i < ctx->cfg_worker_threads; i++) {
 		ctx->client_wait_events[i] = event_create();
 		if (ctx->client_wait_events[i] == 0) {
-			mg_cry_ctx_internal(ctx, "Error creating worker event %i", i);
+			const char *err_msg = "Error creating worker event %i";
+			mg_cry_ctx_internal(ctx, err_msg, i);
 			while (i > 0) {
 				i--;
 				event_destroy(ctx->client_wait_events[i]);
@@ -18802,6 +19095,15 @@ mg_start(const struct mg_callbacks *callbacks,
 			mg_free(ctx->client_socks);
 			mg_free(ctx->client_wait_events);
 			mg_free(ctx->worker_threadids);
+
+			if ((error != NULL) && (error->text_buffer_size > 0)) {
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for error buffers */
+				            error->text,
+				            error->text_buffer_size,
+				            err_msg,
+				            i);
+			}
 			free_context(ctx);
 			pthread_setspecific(sTlsKey, NULL);
 			return NULL;
@@ -18809,10 +19111,19 @@ mg_start(const struct mg_callbacks *callbacks,
 	}
 #endif
 
-
 #if defined(USE_TIMERS)
 	if (timers_init(ctx) != 0) {
-		mg_cry_ctx_internal(ctx, "%s", "Error creating timers");
+		const char *err_msg = "Error creating timers";
+		mg_cry_ctx_internal(ctx, "%s", err_msg);
+
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            err_msg);
+		}
 		free_context(ctx);
 		pthread_setspecific(sTlsKey, NULL);
 		return NULL;
@@ -18823,6 +19134,9 @@ mg_start(const struct mg_callbacks *callbacks,
 	if (ctx->callbacks.init_context) {
 		ctx->callbacks.init_context(ctx);
 	}
+
+	/* From now, the context is successfully created.
+	 * When it is destroyed, the exit callback should be called. */
 	ctx->callbacks.exit_context = exit_callback;
 	ctx->context_type = CONTEXT_SERVER; /* server context */
 
@@ -18838,16 +19152,38 @@ mg_start(const struct mg_callbacks *callbacks,
 		                            &ctx->worker_threadids[i])
 		    != 0) {
 
+			long error_no = (long)ERRNO;
+
 			/* thread was not created */
 			if (i > 0) {
+				/* If the second, third, ... thread cannot be created, set a
+				 * warning, but keep running. */
 				mg_cry_ctx_internal(ctx,
 				                    "Cannot start worker thread %i: error %ld",
 				                    i + 1,
-				                    (long)ERRNO);
+				                    error_no);
+
+				/* If the server initialization should stop here, all threads
+				 * that have already been created must be stopped first, before
+				 * any free_context(ctx) call.
+				 */
+
 			} else {
+				/* If the first worker thread cannot be created, stop
+				 * initialization and free the entire server context. */
 				mg_cry_ctx_internal(ctx,
 				                    "Cannot create threads: error %ld",
-				                    (long)ERRNO);
+				                    error_no);
+
+				if ((error != NULL) && (error->text_buffer_size > 0)) {
+					mg_snprintf(
+					    NULL,
+					    NULL, /* No truncation check for error buffers */
+					    error->text,
+					    error->text_buffer_size,
+					    "Cannot create first worker thread: error %ld",
+					    error_no);
+				}
 				free_context(ctx);
 				pthread_setspecific(sTlsKey, NULL);
 				return NULL;
@@ -18861,10 +19197,26 @@ mg_start(const struct mg_callbacks *callbacks,
 }
 
 
+struct mg_context *
+mg_start(const struct mg_callbacks *callbacks,
+         void *user_data,
+         const char **options)
+{
+	struct mg_init_data init = {0};
+	init.callbacks = callbacks;
+	init.user_data = user_data;
+	init.configuration_options = options;
+
+	return mg_start2(&init, NULL);
+}
+
+
 #if defined(MG_EXPERIMENTAL_INTERFACES)
 /* Add an additional domain to an already running web server. */
 int
-mg_start_domain(struct mg_context *ctx, const char **options)
+mg_start_domain2(struct mg_context *ctx,
+                 const char **options,
+                 struct mg_error_data *error)
 {
 	const char *name;
 	const char *value;
@@ -18873,7 +19225,34 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 	struct mg_domain_context *dom;
 	int idx, i;
 
-	if ((ctx == NULL) || (ctx->stop_flag != 0) || (options == NULL)) {
+	if (error != NULL) {
+		error->code = 0;
+		if (error->text_buffer_size > 0) {
+			*error->text = 0;
+		}
+	}
+
+	if ((ctx == NULL) || (options == NULL)) {
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "Invalid parameters");
+		}
+		return -1;
+	}
+
+	if (ctx->stop_flag != 0) {
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "Server already stopped");
+		}
 		return -1;
 	}
 
@@ -18882,6 +19261,14 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 
 	if (!new_dom) {
 		/* Out of memory */
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "Out or memory");
+		}
 		return -6;
 	}
 
@@ -18889,14 +19276,31 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 	while (options && (name = *options++) != NULL) {
 		if ((idx = get_option_index(name)) == -1) {
 			mg_cry_ctx_internal(ctx, "Invalid option: %s", name);
+			if ((error != NULL) && (error->text_buffer_size > 0)) {
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for error buffers */
+				            error->text,
+				            error->text_buffer_size,
+				            "Invalid option: %s",
+				            name);
+			}
 			mg_free(new_dom);
 			return -2;
 		} else if ((value = *options++) == NULL) {
 			mg_cry_ctx_internal(ctx, "%s: option value cannot be NULL", name);
+			if ((error != NULL) && (error->text_buffer_size > 0)) {
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for error buffers */
+				            error->text,
+				            error->text_buffer_size,
+				            "Invalid option value: %s",
+				            name);
+			}
 			mg_free(new_dom);
 			return -2;
 		}
 		if (new_dom->config[idx] != NULL) {
+			/* Duplicate option: Later values overwrite earlier ones. */
 			mg_cry_ctx_internal(ctx, "warning: %s: duplicate option", name);
 			mg_free(new_dom->config[idx]);
 		}
@@ -18908,6 +19312,14 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 	/* TODO: Maybe use a new option hostname? */
 	if (!new_dom->config[AUTHENTICATION_DOMAIN]) {
 		mg_cry_ctx_internal(ctx, "%s", "authentication domain required");
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "Mandatory option %s missing",
+			            config_options[AUTHENTICATION_DOMAIN].name);
+		}
 		mg_free(new_dom);
 		return -4;
 	}
@@ -18933,6 +19345,14 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 
 	if (!init_ssl_ctx(ctx, new_dom)) {
 		/* Init SSL failed */
+		if ((error != NULL) && (error->text_buffer_size > 0)) {
+			mg_snprintf(NULL,
+			            NULL, /* No truncation check for error buffers */
+			            error->text,
+			            error->text_buffer_size,
+			            "%s",
+			            "Initializing SSL context failed");
+		}
 		mg_free(new_dom);
 		return -3;
 	}
@@ -18949,6 +19369,15 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 			mg_cry_ctx_internal(ctx,
 			                    "domain %s already in use",
 			                    new_dom->config[AUTHENTICATION_DOMAIN]);
+			if ((error != NULL) && (error->text_buffer_size > 0)) {
+				mg_snprintf(NULL,
+				            NULL, /* No truncation check for error buffers */
+				            error->text,
+				            error->text_buffer_size,
+				            "Domain %s specified by %s is already in use",
+				            new_dom->config[AUTHENTICATION_DOMAIN],
+				            config_options[AUTHENTICATION_DOMAIN].name);
+			}
 			mg_free(new_dom);
 			return -5;
 		}
@@ -18968,6 +19397,14 @@ mg_start_domain(struct mg_context *ctx, const char **options)
 	/* Return domain number */
 	return idx;
 }
+
+
+int
+mg_start_domain(struct mg_context *ctx, const char **options)
+{
+	return mg_start_domain2(ctx, options, NULL);
+}
+
 #endif