blob: ca7c93da771a962cbf1b9fccbc625f4d9b3526cd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* http_admin.c
*
* \date May 24, 2019
* \author <a href="mailto:dev@celix.apache.org">Apache Celix Project Team</a>
* \copyright Apache License, Version 2.0
*/
#include <stdlib.h>
#include <memory.h>
#include <limits.h>
#include <unistd.h>
#include "http_admin.h"
#include "http_admin/api.h"
#include "service_tree.h"
#include "civetweb.h"
#include "celix_api.h"
#include "celix_utils_api.h"
struct http_admin_manager {
celix_bundle_context_t *context;
struct mg_context *mgCtx;
char *root;
celix_thread_mutex_t admin_lock; //protects below
celix_http_info_service_t infoSvc;
long infoSvcId;
celix_array_list_t *aliasList; //Array list of http_alias_t
service_tree_t http_svc_tree;
};
typedef struct http_alias {
char *url;
char *alias_path;
long bundle_id;
} http_alias_t;
//Local function prototypes
static int http_request_handle(struct mg_connection *connection);
static void httpAdmin_updateInfoSvc(http_admin_manager_t *admin);
static void createAliasesSymlink(const char *aliases, const char *admin_root, const char *bundle_root, long bundle_id, celix_array_list_t *alias_list);
static bool aliasList_containsAlias(celix_array_list_t *alias_list, const char *alias);
http_admin_manager_t *httpAdmin_create(celix_bundle_context_t *context, char *root, const char **svr_opts) {
celix_status_t status;
struct mg_callbacks callbacks;
http_admin_manager_t *admin = (http_admin_manager_t *) calloc(1, sizeof(http_admin_manager_t));
admin->context = context;
admin->root = root;
admin->infoSvcId = -1L;
status = celixThreadMutex_create(&admin->admin_lock, NULL);
admin->aliasList = celix_arrayList_create();
if (status == CELIX_SUCCESS) {
//Use only begin_request callback
memset(&callbacks, 0, sizeof(callbacks));
callbacks.begin_request = http_request_handle;
admin->mgCtx = mg_start(&callbacks, admin, svr_opts);
status = (admin->mgCtx == NULL ? CELIX_BUNDLE_EXCEPTION : CELIX_SUCCESS);
}
if (status == CELIX_SUCCESS) {
//When webserver started successful, set port info in HTTP admin service
const char *ports = mg_get_option(admin->mgCtx, "listening_ports");
httpAdmin_updateInfoSvc(admin);
fprintf(stdout, "HTTP Admin started at port %s\n", ports);
}
if (status != CELIX_SUCCESS) {
if (admin->mgCtx != NULL) {
mg_stop(admin->mgCtx);
}
celixThreadMutex_destroy(&admin->admin_lock);
celix_arrayList_destroy(admin->aliasList);
free(admin);
admin = NULL;
}
return admin;
}
void httpAdmin_destroy(http_admin_manager_t *admin) {
celixThreadMutex_lock(&(admin->admin_lock));
if(admin->mgCtx != NULL) {
mg_stop(admin->mgCtx);
}
celix_bundleContext_unregisterService(admin->context, admin->infoSvcId);
destroyServiceTree(&admin->http_svc_tree);
//Destroy alias map by removing symbolic links first.
unsigned int size = celix_arrayList_size(admin->aliasList);
for (unsigned int i = (size - 1); i < size; i--) {
http_alias_t *alias = celix_arrayList_get(admin->aliasList, i);
//Delete alias in cache directory
if (remove(alias->alias_path) < 0)
fprintf(stdout, "remove of %s failed\n", alias->alias_path);
free(alias->url);
free(alias->alias_path);
free(alias);
celix_arrayList_removeAt(admin->aliasList, i);
}
celix_arrayList_destroy(admin->aliasList);
celixThreadMutex_unlock(&(admin->admin_lock));
celixThreadMutex_destroy(&(admin->admin_lock));
free(admin->root);
free(admin);
}
struct mg_context *httpAdmin_getServerContext(const http_admin_manager_t *admin) {
return admin->mgCtx;
}
void http_admin_addHttpService(void *handle, void *svc, const celix_properties_t *props) {
http_admin_manager_t *admin = (http_admin_manager_t *) handle;
celix_http_service_t *httpSvc = (celix_http_service_t *) svc;
const char *uri = celix_properties_get(props, HTTP_ADMIN_URI, NULL);
if(uri != NULL) {
celixThreadMutex_lock(&(admin->admin_lock));
if(!addServiceNode(&admin->http_svc_tree, uri, httpSvc)) {
printf("HTTP service with URI %s already exists!\n", uri);
}
celixThreadMutex_unlock(&(admin->admin_lock));
}
}
void http_admin_removeHttpService(void *handle, void *svc __attribute__((unused)), const celix_properties_t *props) {
http_admin_manager_t *admin = (http_admin_manager_t *) handle;
const char *uri = celix_properties_get(props, HTTP_ADMIN_URI, NULL);
if(uri != NULL) {
celixThreadMutex_lock(&(admin->admin_lock));
service_tree_node_t *node = NULL;
node = findServiceNodeInTree(&admin->http_svc_tree, uri);
if(node != NULL){
destroyServiceNode(&admin->http_svc_tree, node, &admin->http_svc_tree.tree_node_count, &admin->http_svc_tree.tree_svc_count);
} else {
printf("Couldn't remove HTTP service with URI: %s, it doesn't exist\n", uri);
}
celixThreadMutex_unlock(&(admin->admin_lock));
}
}
int http_request_handle(struct mg_connection *connection) {
int ret_status = 400; //Default bad request
if(connection != NULL) {
const struct mg_request_info *ri = mg_get_request_info(connection);
http_admin_manager_t *admin = (http_admin_manager_t *) ri->user_data;
service_tree_node_t *node = NULL;
if(mg_get_header(connection, "Upgrade") != NULL) {
//Assume this is a websocket request...
ret_status = 0; //... so return zero to let the civetweb server handle the request.
}
else {
const char *req_uri = ri->request_uri;
node = findServiceNodeInTree(&admin->http_svc_tree, req_uri);
if(node != NULL) {
//Requested URI with node exists, now obtain the http service and call the requested function.
celix_http_service_t *httpSvc = (celix_http_service_t *) node->svc_data->service;
if (strcmp("GET", ri->request_method) == 0) {
if (httpSvc->doGet != NULL) {
ret_status = httpSvc->doGet(httpSvc->handle, connection, req_uri);
} else {
ret_status = 0; //Let civetweb handle the request
}
} else if (strcmp("HEAD", ri->request_method) == 0) {
if (httpSvc->doHead != NULL) {
ret_status = httpSvc->doHead(httpSvc->handle, connection, req_uri);
} else {
ret_status = 0; //Let civetweb handle the request
}
} else if (strcmp("POST", ri->request_method) == 0) {
if (httpSvc->doPost != NULL) {
int bytes_read = 0;
bool no_data = false;
char *rcv_buf = NULL;
if (ri->content_length > 0) {
int content_size = (ri->content_length > INT_MAX ? INT_MAX : (int) ri->content_length);
rcv_buf = malloc((size_t) content_size + 1);
rcv_buf[content_size] = '\0';
bytes_read = mg_read(connection, rcv_buf, (size_t) content_size);
} else {
no_data = true;
}
if (bytes_read > 0 || no_data) {
ret_status = httpSvc->doPost(httpSvc->handle, connection, rcv_buf, (size_t) bytes_read);
} else {
mg_send_http_error(connection, 400, "%s", "Bad request");
ret_status = 400; //Bad Request, failed to read data
}
if (rcv_buf != NULL) {
free(rcv_buf);
}
} else {
ret_status = 0; //Let civetweb handle the request
}
} else if (strcmp("PUT", ri->request_method) == 0) {
if (httpSvc->doPut != NULL) {
int bytes_read = 0;
bool no_data = false;
char *rcv_buf = NULL;
if (ri->content_length > 0) {
int content_size = (ri->content_length > INT_MAX ? INT_MAX : (int) ri->content_length);
rcv_buf = malloc((size_t) content_size + 1);
rcv_buf[content_size] = '\0';
bytes_read = mg_read(connection, rcv_buf, (size_t) content_size);
} else {
no_data = true;
}
if (bytes_read > 0 || no_data) {
ret_status = httpSvc->doPut(httpSvc->handle, connection, req_uri, rcv_buf,
(size_t) bytes_read);
} else {
mg_send_http_error(connection, 400, "%s", "Bad request");
ret_status = 400; //Bad Request, failed to read data
}
if (rcv_buf != NULL) {
free(rcv_buf);
}
} else {
ret_status = 0; //Let civetweb handle the request
}
} else if (strcmp("DELETE", ri->request_method) == 0) {
if (httpSvc->doDelete != NULL) {
ret_status = httpSvc->doDelete(httpSvc->handle, connection, req_uri);
} else {
ret_status = 0; //Let civetweb handle the request
}
} else if (strcmp("TRACE", ri->request_method) == 0) {
if (httpSvc->doTrace != NULL) {
int bytes_read = 0;
bool no_data = false;
char *rcv_buf = NULL;
if (ri->content_length > 0) {
int content_size = (ri->content_length > INT_MAX ? INT_MAX : (int) ri->content_length);
rcv_buf = malloc((size_t) content_size + 1);
rcv_buf[content_size] = '\0';
bytes_read = mg_read(connection, rcv_buf, (size_t) content_size);
} else {
no_data = true;
}
if (bytes_read > 0 || no_data) {
ret_status = httpSvc->doTrace(httpSvc->handle, connection, rcv_buf, (size_t) bytes_read);
} else {
mg_send_http_error(connection, 400, "%s", "Bad request");
ret_status = 400; //Bad Request, failed to read data
}
if (rcv_buf != NULL) {
free(rcv_buf);
}
} else {
ret_status = 0; //Let civetweb handle the request
}
} else if (strcmp("OPTIONS", ri->request_method) == 0) {
if (httpSvc->doOptions != NULL) {
ret_status = httpSvc->doOptions(httpSvc->handle, connection);
} else {
ret_status = 0; //Let civetweb handle the request
}
} else if (strcmp("PATCH", ri->request_method) == 0) {
if (httpSvc->doPatch != NULL) {
int bytes_read = 0;
bool no_data = false;
char *rcv_buf = NULL;
if (ri->content_length > 0) {
int content_size = (ri->content_length > INT_MAX ? INT_MAX : (int) ri->content_length);
rcv_buf = malloc((size_t) content_size + 1);
rcv_buf[content_size] = '\0';
bytes_read = mg_read(connection, rcv_buf, (size_t) content_size);
} else {
no_data = true;
}
if (bytes_read > 0 || no_data) {
ret_status = httpSvc->doPatch(httpSvc->handle, connection, req_uri, rcv_buf,
(size_t) bytes_read);
} else {
mg_send_http_error(connection, 400, "%s", "Bad request");
ret_status = 400; //Bad Request, failed to read data
}
if (rcv_buf != NULL) {
free(rcv_buf);
}
} else {
ret_status = 0; //Let civetweb handle the request
}
} else {
mg_send_http_error(connection, 501, "%s", "Not found");
ret_status = 501; //Not implemented...
}
} else {
ret_status = 0; //Not found requested URI, let civetweb handle this situation
}
}
} else {
mg_send_http_error(connection, 400, "%s", "Bad request");
}
return ret_status;
}
static void httpAdmin_updateInfoSvc(http_admin_manager_t *admin) {
const char *ports = mg_get_option(admin->mgCtx, "listening_ports");
char *resources_urls = NULL;
size_t resources_urls_size;
FILE *stream = open_memstream(&resources_urls, &resources_urls_size);
unsigned int size = arrayList_size(admin->aliasList);
for (unsigned int i = 0; i < size; ++i) {
http_alias_t *alias = arrayList_get(admin->aliasList, i);
bool isLast = (i == size-1);
const char *separator = isLast ? "" : ",";
fprintf(stream, "%s%s", alias->url, separator);
}
fclose(stream);
celixThreadMutex_lock(&admin->admin_lock);
celix_bundleContext_unregisterService(admin->context, admin->infoSvcId);
celix_properties_t *properties = celix_properties_create();
celix_properties_set(properties, HTTP_ADMIN_INFO_PORT, ports);
if (resources_urls_size > 1) {
celix_properties_set(properties, HTTP_ADMIN_INFO_RESOURCE_URLS, resources_urls);
}
admin->infoSvcId = celix_bundleContext_registerService(admin->context, &admin->infoSvc, HTTP_ADMIN_INFO_SERVICE_NAME, properties);
celixThreadMutex_unlock(&admin->admin_lock);
free(resources_urls);
}
/**
* Decode aliases from the given alias string and make a symbolic link for proper resource behavior.
* Save the created aliases as pair with the corresponding bundle id in the given hash map.
*
* @param aliases String which contains the aliases to append to the alias map
* @param admin_root Root path of bundle where the admin runs
* @param bundle_root Bundle path of bundle where the resources remain
* @param bundle_id Bundle ID to connect aliases to this bundle
* @param alias_map Pointer to the alias map to which the created symlink is saved with the bundle id as key
*/
static void createAliasesSymlink(const char *aliases, const char *admin_root, const char *bundle_root, long bundle_id, celix_array_list_t *alias_list) {
char *token = NULL;
char *sub_token = NULL;
char *save_ptr = NULL;
char *sub_save_ptr = NULL;
char *alias_cpy;
if(aliases != NULL && alias_list != NULL && admin_root != NULL && bundle_root != NULL) {
token = alias_cpy = strdup(aliases);
token = strtok_r(token, ",", &save_ptr);
while(token != NULL) {
int i;
char *alias_path;
char *bnd_resource_path;
char cwdBuf[1024] = {0};
char *cwd = getcwd(cwdBuf, sizeof(cwdBuf));
while(isspace(*token)) token++; //skip spaces at beginning
for (i = (int)(strlen(token) - 1); (isspace(token[i])); i--); //skip spaces at end
token[i+1] = '\0';
sub_token = strtok_r(token, ";", &sub_save_ptr);
if(sub_token == NULL) {
token = strtok_r(NULL, ",", &save_ptr);
continue; //Invalid alias to path, continue to next entry
}
asprintf(&alias_path, "%s/%s%s", cwd, admin_root, sub_token);
if(aliasList_containsAlias(alias_list, alias_path)) {
free(alias_path);
continue; //Alias already existing, Continue to next entry
}
sub_token = strtok_r(NULL, ";", &sub_save_ptr);
if(sub_token == NULL) {
free(alias_path);
token = strtok_r(NULL, ",", &save_ptr);
continue; //Invalid alias to path, continue to next entry
}
asprintf(&bnd_resource_path, "%s/%s/%s", cwd, bundle_root, sub_token);
if(symlink(bnd_resource_path, alias_path) == 0) {
http_alias_t *alias = calloc(1, sizeof(*alias));
alias->url = strndup(token, 1024 * 1024 * 10);
alias->alias_path = alias_path;
alias->bundle_id = bundle_id;
celix_arrayList_add(alias_list, alias);
} else {
free(alias_path);
}
free(bnd_resource_path);
token = strtok_r(NULL, ",", &save_ptr);
}
free(alias_cpy);
}
}
/**
* Check if the given alias is already part of the given alias list
*
* @param alias_list The list to search for aliases
* @param alias The alias to search for
* @return true if alias is already in the list, false if not.
*/
static bool aliasList_containsAlias(celix_array_list_t *alias_list, const char *alias) {
unsigned int size = arrayList_size(alias_list);
for(unsigned int i = 0; i < size; i++) {
http_alias_t *http_alias = arrayList_get(alias_list, i);
if(strcmp(http_alias->alias_path, alias) == 0) {
return true;
}
}
return false;
}
void http_admin_startBundle(void *data, const celix_bundle_t *bundle) {
bundle_archive_pt archive = NULL;
bundle_revision_pt revision = NULL;
manifest_pt manifest = NULL;
http_admin_manager_t *admin = data;
// Retrieve manifest from current bundle revision (retrieve revision from bundle archive) to check for
// Amdatu pattern manifest property X-Web-Resource. This property is used for aliases.
celix_status_t status = bundle_getArchive((celix_bundle_t*)bundle, &archive);
if(status == CELIX_SUCCESS && archive != NULL) {
status = bundleArchive_getCurrentRevision(archive, &revision);
}
if(status == CELIX_SUCCESS && revision != NULL) {
status = bundleRevision_getManifest(revision, &manifest);
}
if(status == CELIX_SUCCESS && manifest != NULL) {
const char *aliases = NULL;
const char *revision_root = NULL;
long bnd_id;
aliases = manifest_getValue(manifest, "X-Web-Resource");
bnd_id = celix_bundle_getId(bundle);
bundleRevision_getRoot(revision, &revision_root);
createAliasesSymlink(aliases, admin->root, revision_root, bnd_id, admin->aliasList);
}
httpAdmin_updateInfoSvc(admin);
}
void http_admin_stopBundle(void *data, const celix_bundle_t *bundle) {
http_admin_manager_t *admin = data;
long bundle_id = celix_bundle_getId(bundle);
//Remove all aliases which are connected to this bundle
unsigned int size = arrayList_size(admin->aliasList);
for (unsigned int i = (size - 1); i < size; i--) {
http_alias_t *alias = arrayList_get(admin->aliasList, i);
if(alias->bundle_id == bundle_id) {
remove(alias->alias_path); //Delete alias in cache directory
free(alias->alias_path);
free(alias);
celix_arrayList_removeAt(admin->aliasList, i);
}
}
httpAdmin_updateInfoSvc(admin);
}