diff --git a/src/lib/flow/Kconfig b/src/lib/flow/Kconfig index 6e22b949a..ca08febee 100644 --- a/src/lib/flow/Kconfig +++ b/src/lib/flow/Kconfig @@ -77,4 +77,5 @@ source "src/modules/flow/udev/Kconfig" source "src/modules/flow/unix-socket/Kconfig" source "src/modules/flow/wallclock/Kconfig" source "src/modules/flow/http-server/Kconfig" +source "src/modules/flow/http-client/Kconfig" endmenu diff --git a/src/modules/flow/http-client/Kconfig b/src/modules/flow/http-client/Kconfig new file mode 100644 index 000000000..f4a927888 --- /dev/null +++ b/src/modules/flow/http-client/Kconfig @@ -0,0 +1,6 @@ +config FLOW_NODE_TYPE_HTTP_CLIENT + tristate "Node type: http_client" + depends on HTTP_CLIENT + default m + help + Provides an HTTP client node for basic packet types. diff --git a/src/modules/flow/http-client/Makefile b/src/modules/flow/http-client/Makefile new file mode 100644 index 000000000..0ad62c0cd --- /dev/null +++ b/src/modules/flow/http-client/Makefile @@ -0,0 +1,3 @@ +obj-$(FLOW_NODE_TYPE_HTTP_CLIENT) += http-client.mod +obj-http-client-$(FLOW_NODE_TYPE_HTTP_CLIENT) := http-client.json http-client.o +obj-http-client-$(FLOW_NODE_TYPE_HTTP_CLIENT)-type := flow diff --git a/src/modules/flow/http-client/http-client.c b/src/modules/flow/http-client/http-client.c new file mode 100644 index 000000000..c56507c66 --- /dev/null +++ b/src/modules/flow/http-client/http-client.c @@ -0,0 +1,261 @@ +/* + * This file is part of the Soletta Project + * + * Copyright (C) 2015 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +#include "sol-flow/http-client.h" +#include "sol-flow.h" +#include "sol-http.h" +#include "sol-http-client.h" +#include "sol-json.h" +#include "sol-mainloop.h" +#include "sol-util.h" +#include "sol-vector.h" +#include "sol_config.h" + +struct http_data { + struct sol_ptr_vector pending_conns; + struct sol_str_slice key; + char *url; +}; + +static void +get_key(struct http_data *mdata) +{ + char *ptr = NULL; + + if (strstartswith(mdata->url, "http://")) + ptr = strstr(mdata->url + strlen("http://"), "/"); + else if (strstartswith(mdata->url, "https://")) + ptr = strstr(mdata->url + strlen("https://"), "/"); + + if (ptr) + mdata->key = sol_str_slice_from_str(ptr); + else + mdata->key = (struct sol_str_slice){.len = 0, .data = NULL }; +} + +static void +boolean_request_finished(void *data, + const struct sol_http_client_connection *connection, + struct sol_http_response *response) +{ + struct sol_flow_node *node = data; + struct http_data *mdata = sol_flow_node_get_private_data(node); + bool result = true; + + if (sol_ptr_vector_remove(&mdata->pending_conns, connection) < 0) + SOL_WRN("Failed to find pending connection %p", connection); + + if (!response) { + sol_flow_send_error_packet(node, EINVAL, + "Error while reaching %s", mdata->url); + return; + } + SOL_HTTP_RESPONSE_CHECK_API(response); + + if (!response->content.used) { + sol_flow_send_error_packet(node, EINVAL, + "Empty response from %s", mdata->url); + return; + } + + if (response->response_code != SOL_HTTP_STATUS_OK) { + sol_flow_send_error_packet(node, EINVAL, + "%s returned an unknown response code: %d", + mdata->url, response->response_code); + return; + } + + if (streq(response->content_type, "application/json")) { + struct sol_json_scanner scanner; + struct sol_json_token token, key, value; + enum sol_json_loop_reason reason; + + sol_json_scanner_init(&scanner, response->content.data, response->content.used); + SOL_JSON_SCANNER_OBJECT_LOOP (&scanner, &token, &key, &value, reason) { + if (!sol_json_token_str_eq(&token, mdata->key.data, mdata->key.len)) + continue; + if (sol_json_token_get_type(&value) == SOL_JSON_TYPE_TRUE) + result = true; + else if (sol_json_token_get_type(&value) == SOL_JSON_TYPE_FALSE) + result = false; + else + goto err; + sol_flow_send_boolean_packet(node, + SOL_FLOW_NODE_TYPE_HTTP_CLIENT_BOOLEAN__OUT__OUT, result); + return; + } + } else { + if (!strncasecmp("true", response->content.data, response->content.used)) + result = true; + else if (!strncasecmp("false", response->content.data, response->content.used)) + result = false; + else + goto err; + sol_flow_send_boolean_packet(node, + SOL_FLOW_NODE_TYPE_HTTP_CLIENT_BOOLEAN__OUT__OUT, result); + return; + } + +err: + sol_flow_send_error_packet(node, EINVAL, + "%s Could not parser the url's contents ", mdata->url); +} + +static int +boolean_get_process(struct sol_flow_node *node, void *data, uint16_t port, uint16_t conn_id, + const struct sol_flow_packet *packet) +{ + int r; + struct sol_http_param params; + struct http_data *mdata = data; + struct sol_http_client_connection *connection; + + SOL_NULL_CHECK(mdata->url, -EINVAL); + + sol_http_param_init(¶ms); + if (!sol_http_param_add(¶ms, + SOL_HTTP_REQUEST_PARAM_HEADER("Accept", "application/json"))) { + SOL_WRN("Failed to set query params"); + sol_http_param_free(¶ms); + return -ENOMEM; + } + + connection = sol_http_client_request(SOL_HTTP_METHOD_GET, mdata->url, + ¶ms, boolean_request_finished, node); + + sol_http_param_free(¶ms); + + SOL_NULL_CHECK(connection, -ENOTCONN); + + r = sol_ptr_vector_append(&mdata->pending_conns, connection); + if (r < 0) { + SOL_WRN("Failed to keep pending connection."); + sol_http_client_connection_cancel(connection); + return -ENOMEM; + } + + return 0; +} + +static int +boolean_post_process(struct sol_flow_node *node, void *data, uint16_t port, uint16_t conn_id, + const struct sol_flow_packet *packet) +{ + int r; + bool b; + struct sol_http_param params; + struct http_data *mdata = data; + struct sol_http_client_connection *connection; + + SOL_NULL_CHECK(mdata->url, -EINVAL); + + r = sol_flow_packet_get_boolean(packet, &b); + SOL_INT_CHECK(r, < 0, r); + + sol_http_param_init(¶ms); + if (!(sol_http_param_add(¶ms, + SOL_HTTP_REQUEST_PARAM_HEADER("Accept", "application/json"))) || + !(sol_http_param_add(¶ms, + SOL_HTTP_REQUEST_PARAM_POST_FIELD("value", b ? "true" : "false")))) { + SOL_WRN("Failed to set query params"); + sol_http_param_free(¶ms); + return -ENOMEM; + } + connection = sol_http_client_request(SOL_HTTP_METHOD_POST, mdata->url, + ¶ms, boolean_request_finished, node); + sol_http_param_free(¶ms); + + SOL_NULL_CHECK(connection, -ENOTCONN); + + r = sol_ptr_vector_append(&mdata->pending_conns, connection); + if (r < 0) { + SOL_WRN("Failed to keep pending connection."); + sol_http_client_connection_cancel(connection); + return -ENOMEM; + } + + return 0; +} + +static int +boolean_url_process(struct sol_flow_node *node, void *data, uint16_t port, uint16_t conn_id, + const struct sol_flow_packet *packet) +{ + int r; + const char *url; + struct http_data *mdata = data; + + r = sol_flow_packet_get_string(packet, &url); + SOL_INT_CHECK(r, < 0, r); + + mdata->url = strdup(url); + SOL_NULL_CHECK(mdata->url, -ENOMEM); + get_key(mdata); + + return 0; +} + +static void +boolean_close(struct sol_flow_node *node, void *data) +{ + struct sol_http_client_connection *connection; + struct http_data *mdata = data; + uint16_t i; + + free(mdata->url); + SOL_PTR_VECTOR_FOREACH_IDX (&mdata->pending_conns, connection, i) + sol_http_client_connection_cancel(connection); + sol_ptr_vector_clear(&mdata->pending_conns); +} + +static int +boolean_open(struct sol_flow_node *node, void *data, const struct sol_flow_node_options *options) +{ + struct http_data *mdata = data; + struct sol_flow_node_type_http_client_boolean_options *opts = + (struct sol_flow_node_type_http_client_boolean_options *)options; + + mdata->url = strdup(opts->url); + SOL_NULL_CHECK(mdata->url, -EINVAL); + get_key(mdata); + + sol_ptr_vector_init(&mdata->pending_conns); + + return 0; +} + +#include "http-client-gen.c" diff --git a/src/modules/flow/http-client/http-client.json b/src/modules/flow/http-client/http-client.json new file mode 100644 index 000000000..1dfe9e035 --- /dev/null +++ b/src/modules/flow/http-client/http-client.json @@ -0,0 +1,65 @@ +{ + "$schema": "http://solettaproject.github.io/soletta/schemas/node-type-genspec.schema", + "name": "http-client", + "meta": { + "author": "Intel Corporation", + "license": "BSD-3-Clause", + "version": "1" + }, + "types": [ + { + "name": "http-client/boolean", + "category": "output/network", + "description": "HTTP Client for boolean", + "methods": { + "close": "boolean_close", + "open": "boolean_open" + }, + "options": { + "members": [ + { + "data_type": "string", + "description": "The url that will be used on request and posts", + "name": "url" + } + ], + "version": 1 + }, + "in_ports": [ + { + "data_type": "string", + "description": "Replace the url set on options", + "methods": { + "process": "boolean_url_process" + }, + "name": "URL" + }, + { + "data_type": "any", + "description": "Get the contents of the specified url", + "methods": { + "process": "boolean_get_process" + }, + "name": "GET" + }, + { + "data_type": "boolean", + "description": "Post into the url", + "methods": { + "process": "boolean_post_process" + }, + "name": "POST" + } + ], + "out_ports": [ + { + "data_type": "boolean", + "description": "The value received from the url", + "name": "OUT" + } + ], + "private_data_type": "http_data", + "url": "http://solettaproject.org/doc/latest/node_types/http-client/boolean.html" + } + ] +}