2 * Copyright (C) 2015 "IoT.bzh"
3 * Author "Fulup Ar Foll"
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * Contain all generic part to handle REST/API
21 #include "../include/local-def.h"
27 // handle to hold queryAll values
35 // Helper to retrieve argument from connection
36 PUBLIC const char* getQueryValue(AFB_request * request, char *name) {
39 value = MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
43 STATIC int getQueryCB (void*handle, enum MHD_ValueKind kind, const char *key, const char *value) {
44 queryHandleT *query = (queryHandleT*)handle;
46 query->idx += snprintf (&query->msg[query->idx],query->len," %s: \'%s\',", key, value);
49 // Helper to retrieve argument from connection
50 PUBLIC const char* getQueryAll(AFB_request * request, char *buffer, size_t len) {
57 MHD_get_connection_values (request->connection, MHD_GET_ARGUMENT_KIND, getQueryCB, &query);
62 // Sample Generic Ping Debug API
63 PUBLIC json_object* apiPingTest(AFB_session *session, AFB_request *request, void* handle) {
65 json_object *response;
68 // request all query key/value
69 getQueryAll (request, query, sizeof(query));
71 // check if we have some post data
72 if (request->post == NULL) request->post="NoData";
74 // return response to caller
75 response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon %d query={%s} PostData: \'%s\' ", pingcount++, query, request->post);
80 // Because of POST call multiple time requestApi we need to free POST handle here
82 STATIC void endRequest(void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
83 AFB_HttpPost *posthandle = *con_cls;
85 // if post handle was used let's free everything
87 if (verbose) fprintf(stderr, "End Post Request UID=%d\n", posthandle->uid);
88 free(posthandle->data);
93 // Check of apiurl is declare in this plugin and call it
94 STATIC json_object * callPluginApi(AFB_plugin *plugin, AFB_session *session, AFB_request *request) {
95 json_object *response;
97 int signals[]= {SIGALRM, SIGSEGV, SIGFPE, 0};
99 /*---------------------------------------------------------------
100 | Signal handler defined inside CallPluginApi to access Request
101 +---------------------------------------------------------------- */
102 void pluginError (int signum) {
106 // unlock timeout signal to allow a new signal to come
107 sigemptyset (&sigset);
108 sigaddset (&sigset, SIGALRM);
109 sigprocmask (SIG_UNBLOCK, &sigset, 0);
111 fprintf (stderr, "Oops:%s Plugin Api Timeout timeout\n", configTime());
112 longjmp (request->checkPluginCall, signum);
115 // If a plugin hold this urlpath call its callback
116 for (idx = 0; plugin->apis[idx].callback != NULL; idx++) {
117 if (!strcmp(plugin->apis[idx].name, request->api)) {
119 // save context before calling the API
120 status = setjmp (request->checkPluginCall);
122 response = jsonNewMessage(AFB_FATAL, "Plugin Call Fail prefix=%s api=%s info=%s", plugin->prefix, request->api, plugin->info);
125 if (session->config->apiTimeout > 0) {
126 for (sig=0; signals[sig] != 0; sig++) {
127 if (signal (signals[sig], pluginError) == SIG_ERR) {
128 fprintf (stderr, "%s ERR: main no Signal/timeout handler installed.", configTime());
133 // Trigger a timer to protect plugin for no return API
134 alarm (session->config->apiTimeout);
137 response = plugin->apis[idx].callback(session, request, plugin->apis[idx].handle);
138 if (response != NULL) json_object_object_add(response, "jtype", plugin->jtype);
140 // cancel timeout and plugin signal handle before next call
141 if (session->config->apiTimeout > 0) {
143 for (sig=0; signals[sig] != 0; sig++) {
144 signal (signals[sig], SIG_DFL);
155 // process rest API query
157 PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, const char* url, const char *method
158 , const char *upload_data, size_t *upload_data_size, void **con_cls) {
160 static int postcount = 0; // static counter to debug POST protocol
161 char *baseurl, *baseapi, *urlcpy1, *urlcpy2, *query;
162 json_object *jsonResponse, *errMessage;
163 struct MHD_Response *webResponse;
164 const char *serialized, parsedurl;
166 AFB_HttpPost *posthandle = *con_cls;
169 // Extract plugin urlpath from request and make two copy because strsep overload copy
170 urlcpy1 = urlcpy2 = strdup(url);
171 baseurl = strsep(&urlcpy2, "/");
172 if (baseurl == NULL) {
173 errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s", url);
177 baseapi = strsep(&urlcpy2, "/");
178 if (baseapi == NULL) {
179 errMessage = jsonNewMessage(AFB_FATAL, "Invalid Plugin/API call url=%s/%s", baseurl, url);
184 // if post data may come in multiple calls
185 if (0 == strcmp(method, MHD_HTTP_METHOD_POST)) {
186 const char *encoding, *param;
188 AFB_HttpPost *posthandle = *con_cls;
190 // Let make sure we have the right encoding and a valid length
191 encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
192 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
193 if (param) sscanf(param, "%i", &contentlen);
195 // POST datas may come in multiple chunk. Even when it never happen on AFB, we still have to handle the case
196 if (strcasestr(encoding, JSON_CONTENT) == 0) {
197 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
201 if (contentlen > MAX_POST_SIZE) {
202 errMessage = jsonNewMessage(AFB_FATAL, "Post Date to big %d > %d", contentlen, MAX_POST_SIZE);
206 // In POST mode first libmicrohttp call only establishes POST handling.
207 if (posthandle == NULL) {
208 posthandle = malloc(sizeof (AFB_HttpPost)); // allocate application POST processor handle
209 posthandle->uid = postcount++; // build a UID for DEBUG
210 posthandle->len = 0; // effective length within POST handler
211 posthandle->data = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
212 *con_cls = posthandle; // attache POST handle to current HTTP session
214 if (verbose) fprintf(stderr, "Create Post[%d] Size=%d\n", posthandle->uid, contentlen);
218 // This time we receive partial/all Post data. Note that even if we get all POST data. We should nevertheless
219 // return MHD_YES and not process the request directly. Otherwise Libmicrohttpd is unhappy and fails with
220 // 'Internal application error, closing connection'.
221 if (*upload_data_size) {
222 if (verbose) fprintf(stderr, "Update Post[%d]\n", posthandle->uid);
224 memcpy(&posthandle->data[posthandle->len], upload_data, *upload_data_size);
225 posthandle->len = posthandle->len + *upload_data_size;
226 *upload_data_size = 0;
230 // We should only start to process DATA after Libmicrohttpd call or application handler with *upload_data_size==0
231 // At this level we're may verify that we got everything and process DATA
232 if (posthandle->len != contentlen) {
233 errMessage = jsonNewMessage(AFB_FATAL, "Post Data Incomplete UID=%d Len %d != %s", posthandle->uid, contentlen, posthandle->len);
237 // Before processing data, make sure buffer string is properly ended
238 posthandle->data[posthandle->len] = '\0';
239 request.post = posthandle->data;
241 if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", posthandle->uid, request.post);
248 // build request structure
249 memset(&request, 0, sizeof (request));
250 request.connection = connection;
252 request.plugin = baseurl;
253 request.api = baseapi;
255 // Search for a plugin with this urlpath
256 for (idx = 0; session->plugins[idx] != NULL; idx++) {
257 if (!strcmp(session->plugins[idx]->prefix, baseurl)) {
258 jsonResponse = callPluginApi(session->plugins[idx], session, &request);
263 // No plugin was found
264 if (session->plugins[idx] == NULL) {
265 errMessage = jsonNewMessage(AFB_FATAL, "No Plugin for %s", baseurl);
270 // plugin callback did not return a valid Json Object
271 if (jsonResponse == NULL) {
272 errMessage = jsonNewMessage(AFB_FATAL, "No Plugin/API for %s/%s", baseurl, baseapi);
276 serialized = json_object_to_json_string(jsonResponse);
277 webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
279 ret = MHD_queue_response(connection, MHD_HTTP_OK, webResponse);
280 MHD_destroy_response(webResponse);
281 json_object_put(jsonResponse); // decrease reference rqtcount to free the json object
285 serialized = json_object_to_json_string(errMessage);
286 webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
287 ret = MHD_queue_response(connection, MHD_HTTP_BAD_REQUEST, webResponse);
288 MHD_destroy_response(webResponse);
289 json_object_put(errMessage); // decrease reference rqtcount to free the json object
294 // Loop on plugins. Check that they have the right type, prepare a JSON object with prefix
295 STATIC AFB_plugin ** RegisterPlugins(AFB_plugin **plugins) {
298 for (idx = 0; plugins[idx] != NULL; idx++) {
299 if (plugins[idx]->type != AFB_PLUGIN) {
300 fprintf(stderr, "ERROR: AFSV plugin[%d] invalid type=%d != %d\n", idx, AFB_PLUGIN, plugins[idx]->type);
302 // some sanity controls
303 if ((plugins[idx]->prefix == NULL) || (plugins[idx]->info == NULL) || (plugins[idx]->apis == NULL)) {
304 if (plugins[idx]->prefix == NULL) plugins[idx]->prefix = "No URL prefix for APIs";
305 if (plugins[idx]->info == NULL) plugins[idx]->info = "No Info describing plugin APIs";
306 fprintf(stderr, "ERROR: plugin[%d] invalid prefix=%s info=%s", idx, plugins[idx]->prefix, plugins[idx]->info);
310 if (verbose) fprintf(stderr, "Loading plugin[%d] prefix=[%s] info=%s\n", idx, plugins[idx]->prefix, plugins[idx]->info);
312 // Prepare Plugin name to be added into each API response
313 plugins[idx]->jtype = json_object_new_string(plugins[idx]->prefix);
314 json_object_get(plugins[idx]->jtype); // increase reference count to make it permanent
316 // compute urlprefix lenght
317 plugins[idx]->prefixlen = strlen(plugins[idx]->prefix);
323 void initPlugins(AFB_session *session) {
324 static AFB_plugin * plugins[10];
326 plugins[0] = afsvRegister(session),
327 plugins[1] = dbusRegister(session),
328 plugins[2] = alsaRegister(session),
331 // complete plugins and save them within current sessions
332 session->plugins = RegisterPlugins(plugins);