/*! * Copyright (c) 2015-2018, NADAL Jean-Baptiste. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA * * @Author: NADAL Jean-Baptiste * @Date: 16/06/2017 */ /*------------------------------- INCLUDES ----------------------------------*/ #include #include #include #include #include #include #include #include #include extern "C" { #include #include } #include #include "core/web-connection.h" #include "core/http-header.h" #include "rest/rest-connection.h" #include "rest/rest-controller.h" #include "etag-rest/etag-rest-controller.h" #include "notification/notification-controller.h" #include "uhttp-server.h" #include #include /*------------------------------- GLOBALS ----------------------------------*/ #define kControllerKey "controller" #define kEtagControllerKey "etag_controller" #define kNotificationKey "notification" #define kControllerPathKey "path" #define kControllerModelNameKey "model_name" #define kControllerEventNameKey "event_name" #define kControllerEtagKey "etag_key" #define kControllerGetMethodKey "get_method" #define kControllerSetMethodKey "set_method" #define kControllerRawResponseKey "raw_response" #define kdefaultGetMethod "get" #define kdefaultSetMethod "set" #define kJsonControlerNotFound "{{\n" \ " \"id\": \"{}\",\n" \ " \"status\": \"failed\",\n" \ " \"error\": \"Controller Not Found\",\n" \ " \"response_code\": 404\n}}" #define DEBUG_REQUEST 1 #define BUFFER_SIZE 100 #ifdef DEBUG_REQUEST /*! ---------------------------------------------------------------------------- * @fn log_with_timestamp * * @brief method to log with time stamp. */ void log_with_timestamp(const char *a_msg) { char the_buffer[512]; time_t the_time; struct timeval the_timeval; the_time = time(NULL); strftime(the_buffer, sizeof(the_buffer), "%m/%d/%Y %H:%M:%S", localtime(&the_time)); gettimeofday(&the_timeval, NULL); fprintf(stdout, "%s.%09ld - %s\n", the_buffer, the_timeval.tv_usec * 1000, a_msg); } double time_diff(struct timeval x, struct timeval y) { double x_ms, y_ms, diff; x_ms = (double)x.tv_sec * 1000000 + (double)x.tv_usec; y_ms = (double)y.tv_sec * 1000000 + (double)y.tv_usec; diff = (double)y_ms - (double)x_ms; return diff; } #endif /*! ---------------------------------------------------------------------------- * @fn get_uhttp_server_ctx * * @brief method to return ubux context to c plugin. */ extern "C" struct ubus_context *get_uhttp_server_ctx(void) { return UhttpServer::get_instance().get_context(); } /*! ---------------------------------------------------------------------------- * @fn set_uhttp_server_ctx * * @brief init method from c plugin to uhttp server. */ extern "C" int uhttp_server_setup(struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops, const char *a_config_dir_path) { return UhttpServer::get_instance().setup(a_ctx, an_ops, a_config_dir_path); } /*! ---------------------------------------------------------------------------- * @fn uhttp_server_check_url * * @brief Check if the url is managed by this plugin */ extern "C" bool uhttp_server_check_url(const char *an_url) { return UhttpServer::get_instance().check_url(an_url); } /*! ---------------------------------------------------------------------------- * @fn uhttp_server_handle_request * * @brief Handle the request. */ extern "C" void uhttp_server_handle_request(struct client *a_cl, char *an_url, struct path_info *a_pi) { #ifdef DEBUG_REQUEST gettimeofday(&a_cl->start_request, NULL); char buffer[BUFFER_SIZE]; snprintf(buffer, sizeof(buffer), "uhttp handle_request : client id: %d - %s", a_cl->id, an_url); log_with_timestamp(buffer); #endif UhttpServer::get_instance().handle_request(a_cl, an_url, a_pi); } /*! ---------------------------------------------------------------------------- * @fn uhttp_server_data_send * * @brief callback called when data should . */ static int uhttp_server_data_send(struct client *a_cl, const char *a_data, int a_len) { #ifdef DEBUG_REQUEST log_with_timestamp("uhttp_server_data_send"); #endif return UhttpServer::get_instance().data_send(a_cl, a_data, a_len); } /*! ---------------------------------------------------------------------------- * @fn uhttp_server_data_done * * @brief callback called when the server is ready to launch the request. */ static void uhttp_server_data_done(struct client *a_cl) { UhttpServer::get_instance().invoke_request(a_cl); } /*! ---------------------------------------------------------------------------- * @fn uhttp_server_request_free * * @brief callback call to free the request. */ static void uhttp_server_request_free(struct client *a_cl) { #ifdef DEBUG_REQUEST struct timeval the_current_timeval; gettimeofday(&the_current_timeval, NULL); char buffer[BUFFER_SIZE]; snprintf(buffer, sizeof(buffer), "client id: %d duree: %.0lf ms", a_cl->id, time_diff(a_cl->start_request, the_current_timeval) / 1000); log_with_timestamp(buffer); #endif UhttpServer::get_instance().request_free(a_cl); } /*! ---------------------------------------------------------------------------- * @fn uhttp_server_close_fds * * @brief callback call when the process close. (needed ? to be dfefined). */ static void uhttp_server_close_fds(struct client *cl) { // printf("uhttp_server_close_fds(%p)... client id: %d\n", cl, cl->id); } /*! ---------------------------------------------------------------------------- * @fn get_instance * * @brief singleton to return the instance of UhttpServer. */ UhttpServer &UhttpServer::get_instance(void) { static UhttpServer m_ptr; return m_ptr; } /*! ---------------------------------------------------------------------------- * @fn UhttpServer * * @brief Constructor of the Uhttp Server */ UhttpServer::UhttpServer(void) : m_ctx(NULL), m_ops(NULL) { } /*! ---------------------------------------------------------------------------- * @fn ~UhttpServer * * @brief Destructor of the Uhttp Server */ UhttpServer::~UhttpServer(void) { ControllerIterator it; ConnectionIterator the_cnx_it; for (it = m_controller_array.begin(); it != m_controller_array.end(); ++it) { delete it->second; } for (the_cnx_it = m_connections.begin(); the_cnx_it != m_connections.end(); ++the_cnx_it) { delete the_cnx_it->second; } ubus_free(m_ctx); } /*! ---------------------------------------------------------------------------- * @fn setup * * @brief setup the http server. */ int UhttpServer::setup(struct ubus_context *a_ctx, const struct uhttpd_ops *an_ops, const char *a_config_dir_path) { m_ctx = a_ctx; m_ops = const_cast(an_ops); return load_config_dir(a_config_dir_path); } /*! ---------------------------------------------------------------------------- * @fn get_context * * @brief return the ubus context. */ struct ubus_context *UhttpServer::get_context(void) { return m_ctx; } /*! ---------------------------------------------------------------------------- * @fn check_url * * @brief check if the url is managed by this plugin. */ bool UhttpServer::check_url(const std::string &an_url) { std::list::iterator the_it; // printf("UhttpServer::check_url: %s\n", an_url.c_str()); for (the_it = m_path_list.begin(); the_it != m_path_list.end(); the_it++) { if (an_url.find(*the_it) != std::string::npos) return true; } return false; } /*! ---------------------------------------------------------------------------- * @fn handle_request * * @brief Handle the Request. */ void UhttpServer::handle_request(struct client *a_cl, const std::string &an_url, struct path_info *a_pi) { std::string the_url, the_parameters; std::size_t the_pos; WebController *the_controller; // printf("uhttp_server_handle_request : url: <%s> client: %p\n", an_url.c_str(), a_cl); // Check if parameters are present on the url. the_pos = an_url.find_first_of("?", 0); if (the_pos != std::string::npos) { // parameters are present. the_url = an_url.substr(0, the_pos); the_parameters = an_url.substr(the_pos + 1); // printf("url: <%s> | parameters: <%s>\n", the_url.c_str(), the_parameters.c_str()); } else { the_url = an_url; } the_controller = m_controller_array[the_url]; if (the_controller == NULL) { std::string the_msg; the_msg = fmt::format(kJsonControlerNotFound, the_url); send_error(a_cl, 404, "Not Found", the_msg); return; } // We found the controller. // printf("method: %d\n", a_cl->request.method); struct dispatch *d = &a_cl->dispatch; switch (a_cl->request.method) { case UH_HTTP_MSG_GET: case UH_HTTP_MSG_PUT: case UH_HTTP_MSG_POST: case UH_HTTP_MSG_DELETE: d->data_send = uhttp_server_data_send; d->data_done = uhttp_server_data_done; d->close_fds = uhttp_server_close_fds; d->free = uhttp_server_request_free; m_connections[a_cl->id] = the_controller->new_connection(m_ops, a_cl, the_parameters); break; case UH_HTTP_MSG_OPTIONS: { HttpHeader the_http_header; the_http_header.send_option(m_ops, a_cl, 200); } break; default: // printf("Invalid Request\n"); send_error(a_cl, 400, "Bad Request", "Invalid Request"); } // printf("uhttp_server_handle_request -done.\n"); } /*! ---------------------------------------------------------------------------- * @fn invoke_request * * @brief This method to invoke the request. */ void UhttpServer::invoke_request(struct client *a_client) { uh_client_ref(a_client); // printf("UhttpServer::invoke_request\n"); m_connections[a_client->id]->invoke(m_ctx); uh_client_unref(a_client); } /*! ---------------------------------------------------------------------------- * @fn request_free * * @brief This method free the complete request. */ void UhttpServer::request_free(struct client *a_client) { ConnectionIterator the_it; // printf("UhttpServer::request_free id:%d nb connection: %ld\n", a_client->id, m_connections.size()); uh_client_ref(a_client); the_it = m_connections.find(a_client->id); if (the_it != m_connections.end()) { if ((the_it->second)->should_be_removed()) { delete the_it->second; m_connections.erase(the_it); } } uh_client_unref(a_client); // printf("UhttpServer::request_free-done\n"); } /*! ---------------------------------------------------------------------------- * @fn data_send * * @brief This method called when data arrived from the client. */ int UhttpServer::data_send(struct client *a_client, const char *a_data, int a_len) { return m_connections[a_client->id]->add_data(a_data, a_len); } /*! ---------------------------------------------------------------------------- * @fn load_config_dir * * @brief Load config from the configuration directory */ int UhttpServer::load_config_dir(const char *a_config_dir_path) { DIR *the_rep = NULL; struct dirent *the_dir_ent = NULL; std::string the_path; struct json_object *the_root_node; the_rep = opendir(a_config_dir_path); if (the_rep == NULL) { fprintf (stderr, "Impossible to open the config directory. check your parameters.\n"); return -1; } while ((the_dir_ent = readdir(the_rep)) != NULL) { if (strcmp(the_dir_ent->d_name, ".") != 0 && strcmp(the_dir_ent->d_name, "..") != 0) { // printf ("load: %s\n", the_dir_ent->d_name); the_path = a_config_dir_path; the_path += the_dir_ent->d_name; the_root_node = load(the_path); if (the_root_node == NULL) { fprintf(stderr, "Failed to load controller configuration file (%s).\n", the_path.c_str()); return -1; } add_controller_from_json(the_root_node); json_object_put(the_root_node); } } closedir(the_rep); return 0; } /*! ---------------------------------------------------------------------------- * @fn add_controller_from_json * * @brief Load config from the configuration directory */ int UhttpServer::add_controller_from_json(struct json_object *a_root_node) { struct json_object *the_ctr_array_node, *the_ctrl_node, *the_note_array_node, *the_etag_ctrl_array_node; std::string the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method; bool the_raw_response = false; int the_len; int i; if (json_object_object_get_ex(a_root_node, kControllerKey, &the_ctr_array_node)) { the_len = json_object_array_length(the_ctr_array_node); for (i = 0; i < the_len; i++) { the_ctrl_node = json_object_array_get_idx(the_ctr_array_node, i); if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response)) { #if 0 printf("Path: <%s>\n", the_path.c_str()); printf("\t- model name: <%s>\n", the_model_name.c_str()); printf("\t- get: <%s>\n", the_get_method.c_str()); printf("\t- set: <%s>\n", the_set_method.c_str()); printf("\t- raw response: <%s>\n", the_raw_response?"true":"false"); #endif add_controller(the_path, new RestController(the_model_name, the_get_method, the_set_method, kDefaultTimeout, the_raw_response)); } } } if (json_object_object_get_ex(a_root_node, kEtagControllerKey, &the_etag_ctrl_array_node)) { the_len = json_object_array_length(the_etag_ctrl_array_node); for (i = 0; i < the_len; i++) { the_ctrl_node = json_object_array_get_idx(the_etag_ctrl_array_node, i); if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response)) { EtagRestController *the_etag_rest_controller; the_etag_rest_controller = new EtagRestController(the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, kDefaultTimeout, the_raw_response); add_controller(the_path, the_etag_rest_controller); } } } if (json_object_object_get_ex(a_root_node, kNotificationKey, &the_note_array_node)) { the_len = json_object_array_length(the_note_array_node); for (i = 0; i < the_len; i++) { the_ctrl_node = json_object_array_get_idx(the_note_array_node, i); if (get_controller_fields(the_ctrl_node, the_path, the_model_name, the_event_name, the_etag_key, the_get_method, the_set_method, the_raw_response)) { NotificationController *the_notification_controller; the_notification_controller = new NotificationController(the_event_name); add_controller(the_path, the_notification_controller); } } } return 0; } /*! ---------------------------------------------------------------------------- * @fn get_controller_fields * * @brief This method extract the controller fields from a json node. */ bool UhttpServer::get_controller_fields(struct json_object *a_node, std::string &a_path, std::string &a_model_name, std::string &an_event_name, std::string &an_etag_key, std::string &a_get_method, std::string &a_set_method, bool &a_raw_response) { bool the_result = false; struct json_object *the_path_node, *the_ubus_model_node, *the_ubus_event_node, *the_get_method_node, *the_etag_key_node; struct json_object *the_set_method_node, *the_raw_response_node; // path if (json_object_object_get_ex(a_node, kControllerPathKey, &the_path_node)) { a_path = json_object_get_string(the_path_node); the_result = true; } else { a_path.clear(); } // model_name if (json_object_object_get_ex(a_node, kControllerModelNameKey, &the_ubus_model_node)) { a_model_name = json_object_get_string(the_ubus_model_node); } else { a_model_name.clear(); } // event name if (json_object_object_get_ex(a_node, kControllerEventNameKey, &the_ubus_event_node)) { an_event_name = json_object_get_string(the_ubus_event_node); } else { an_event_name.clear(); } // etag key if (json_object_object_get_ex(a_node, kControllerEtagKey, &the_etag_key_node)) { an_etag_key = json_object_get_string(the_etag_key_node); } else { an_etag_key.clear(); } // get_method if (json_object_object_get_ex(a_node, kControllerGetMethodKey, &the_get_method_node)) { a_get_method = json_object_get_string(the_get_method_node); } else { a_get_method = kdefaultGetMethod; } // set_method if (json_object_object_get_ex(a_node, kControllerSetMethodKey, &the_set_method_node)) { a_set_method = json_object_get_string(the_set_method_node); } else { a_set_method = kdefaultSetMethod; } // raw_response if (json_object_object_get_ex(a_node, kControllerRawResponseKey, &the_raw_response_node)) { a_raw_response = json_object_get_boolean(the_raw_response_node); } else { a_raw_response = false; } return the_result; } /*! ---------------------------------------------------------------------------- * @fn load * * @brief This method load the restd controllers and file it into a json object. * return null otherwise. */ struct json_object *UhttpServer::load(const std::string &a_file) { struct json_object *the_root_node = NULL; struct stat the_stat; int the_fd; char *the_addr; the_fd = open(a_file.c_str(), O_RDONLY); if (the_fd == -1) { perror("open"); return NULL; } if (fstat(the_fd, &the_stat) == -1) { perror("fstat"); close(the_fd); return NULL; } the_addr = (char *)mmap(NULL, the_stat.st_size, PROT_READ, MAP_PRIVATE, the_fd, 0); if (the_addr == MAP_FAILED) { perror("mmap"); close(the_fd); return NULL; } the_root_node = json_tokener_parse(the_addr); close(the_fd); return the_root_node; } /*! ---------------------------------------------------------------------------- * @fn add_rest_controller * * @brief add a rest controller. */ int UhttpServer::add_controller(const std::string &an_uri, WebController *a_controller) { std::string the_path; std::size_t the_pos; if (a_controller == NULL) return -1; a_controller->set_name(an_uri); m_controller_array[an_uri] = a_controller; // printf("add: an_uri:%s\n", an_uri.c_str()); // Keep a list of the API root. to check if a controller is managed by this plugin or not. the_pos = an_uri.find_first_of("/", 1); if (the_pos != std::string::npos) { the_path = an_uri.substr(0, the_pos + 1); if (std::find(m_path_list.begin(), m_path_list.end(), the_path) == m_path_list.end()) { // uniq version. we add it. m_path_list.push_back(the_path); } } return 0; } /*! ---------------------------------------------------------------------------- * @fn send_error * * @brief Send an error message to the client. */ void UhttpServer::send_error(struct client *a_cl, int a_code, const char *a_summary, const std::string &a_msg) { m_ops->http_header(a_cl, a_code, a_summary); ustream_printf(a_cl->us, "Content-Type: application/json\r\n\r\n"); m_ops->chunk_printf(a_cl, "%s", a_msg.c_str()); m_ops->request_done(a_cl); }