Merge origin/master
[src/app-framework-binder.git] / src / rest-api.c
index cc2198d..8146e14 100644 (file)
  * 
  * Contain all generic part to handle REST/API
  * 
- *  https://www.gnu.org/software/libmicrohttpd/tutorial.html [search 'FILE *fp']
+ *  https://www.gnu.org/software/libmicrohttpd/tutorial.html [search 'largepost.c']
  */
 
 #include "../include/local-def.h"
 
+#include <dirent.h>
+#include <dlfcn.h>
 #include <setjmp.h>
 #include <signal.h>
 
 #define AFB_MSG_JTYPE "AJB_reply"
 
 
-// handle to hold queryAll values
-typedef struct {
-     char    *msg;
-     int     idx;
-     size_t  len;
-} queryHandleT;
 
 static json_object     *afbJsonType;
 
 
-// Sample Generic Ping Debug API
-PUBLIC json_object* apiPingTest(AFB_request *request) {
-    static pingcount = 0;
-    json_object *response;
-    char query  [256];
-    char session[256];
-
-    int len;
-    AFB_clientCtx *client=request->client; // get client context from request
-    
-    // request all query key/value
-    len = getQueryAll (request, query, sizeof(query));
-    if (len == 0) strncpy (query, "NoSearchQueryList", sizeof(query));
-    
-    // check if we have some post data
-    if (request->post == NULL)  request->post->data="NoData"; 
-    
-    // check is we have a session and a plugin handle
-    if (client == NULL) strcpy (session,"NoSession");       
-    else snprintf(session, sizeof(session),"uuid=%s token=%s ctx=0x%x handle=0x%x", client->uuid, client->token, client->ctx, client->ctx); 
-        
-    // return response to caller
-    response = jsonNewMessage(AFB_SUCCESS, "Ping Binder Daemon count=%d CtxtId=%d query={%s} session={%s} PostData: [%s] "
-               , pingcount++, request->client->cid, query, session, request->post->data);
-    return (response);
-}
-
-// Helper to retrieve argument from  connection
-PUBLIC const char* getQueryValue(AFB_request * request, char *name) {
-    const char *value;
-
-    value = MHD_lookup_connection_value(request->connection, MHD_GET_ARGUMENT_KIND, name);
-    return (value);
-}
-
-STATIC int getQueryCB (void*handle, enum MHD_ValueKind kind, const char *key, const char *value) {
-    queryHandleT *query = (queryHandleT*)handle;
-        
-    query->idx += snprintf (&query->msg[query->idx],query->len," %s: \'%s\',", key, value);
-}
-
-// Helper to retrieve argument from  connection
-PUBLIC int getQueryAll(AFB_request * request, char *buffer, size_t len) {
-    queryHandleT query;
-    buffer[0] = '\0'; // start with an empty string
-    query.msg= buffer;
-    query.len= len;
-    query.idx= 0;
-
-    MHD_get_connection_values (request->connection, MHD_GET_ARGUMENT_KIND, getQueryCB, &query);
-    return (len);
-}
-
 // Because of POST call multiple time requestApi we need to free POST handle here
+// Note this method is called from http-svc just before closing session
 PUBLIC void endPostRequest(AFB_PostHandle *postHandle) {
 
     if (postHandle->type == AFB_POST_JSON) {
-        if (verbose) fprintf(stderr, "End PostJson Request UID=%d\n", postHandle->uid);
+        // if (verbose) fprintf(stderr, "End PostJson Request UID=%d\n", postHandle->uid);
     }
 
     if (postHandle->type == AFB_POST_FORM) {
-        AFB_PostHandle *postform = (AFB_PostHandle*) postHandle->private;
-        if (verbose) fprintf(stderr, "End PostForm Request UID=%d\n", postHandle->uid);
-
-        // call API termination callback
-        if (!postHandle->private) {
-            if (!postHandle->completeCB) postHandle->completeCB (postHandle->private);
-        }
+         if (verbose) fprintf(stderr, "End PostForm Request UID=%d\n", postHandle->uid);
     }
     free(postHandle->private);
     free(postHandle);
@@ -178,7 +116,13 @@ STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request, void *c
                 if (AFB_SESSION_NONE != plugin->apis[idx].session) {
                     
                     // add client context to request
-                    ctxClientGet(request, plugin);
+                    if (ctxClientGet(request, plugin) != AFB_SUCCESS) {
+                        request->errcode=MHD_HTTP_INSUFFICIENT_STORAGE;
+                        json_object_object_add(jcall, "status", json_object_new_string ("fail"));
+                        json_object_object_add(jcall, "info", json_object_new_string ("Client Session Context Full !!!"));
+                        json_object_object_add(request->jresp, "request", jcall);
+                        return (AFB_DONE);                              
+                    };
                     
                     if (verbose) fprintf(stderr, "Plugin=[%s] Api=[%s] Middleware=[%d] Client=[0x%x] Uuid=[%s] Token=[%s]\n"
                            , request->plugin, request->api, plugin->apis[idx].session, request->client, request->client->uuid, request->client->token);                        
@@ -203,6 +147,7 @@ STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request, void *c
                             } else {
                                 json_object_object_add(jcall, "uuid", json_object_new_string (request->client->uuid));                                
                                 json_object_object_add(jcall, "token", json_object_new_string (request->client->token));                                
+                                json_object_object_add(jcall, "timeout", json_object_new_int (request->config->cntxTimeout));                                
                             }
                             break;
 
@@ -217,6 +162,7 @@ STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request, void *c
                             } else {
                                 json_object_object_add(jcall, "uuid", json_object_new_string (request->client->uuid));                                
                                 json_object_object_add(jcall, "token", json_object_new_string (request->client->token));                                
+                                json_object_object_add(jcall, "timeout", json_object_new_int (request->config->cntxTimeout));                                
                             }
                             break;
 
@@ -248,6 +194,9 @@ STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request, void *c
                 
                 // Effectively call the API with a subset of the context
                 jresp = plugin->apis[idx].callback(request, context);
+                
+                // handle intemediatry Post Iterates out of band
+                if ((jresp == NULL) && (request->errcode == MHD_HTTP_OK)) return (AFB_SUCCESS);
 
                 // Session close is done after the API call so API can still use session in closing API
                 if (AFB_SESSION_CLOSE == plugin->apis[idx].session) ctxTokenReset (request);                    
@@ -273,8 +222,7 @@ STATIC AFB_error callPluginApi(AFB_plugin *plugin, AFB_request *request, void *c
             }       
             return (AFB_DONE);
         }
-    }
-    
+    }   
     return (AFB_FAIL);
 }
 
@@ -282,6 +230,7 @@ STATIC AFB_error findAndCallApi (AFB_request *request, void *context) {
     int idx;
     AFB_error status;
     
+    if (!request->api || !request->plugin) return (AFB_FAIL);
    
     // Search for a plugin with this urlpath
     for (idx = 0; request->plugins[idx] != NULL; idx++) {
@@ -292,18 +241,15 @@ STATIC AFB_error findAndCallApi (AFB_request *request, void *context) {
     }
     // No plugin was found
     if (request->plugins[idx] == NULL) {
-        request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s]", request->plugin);
+        request->jresp = jsonNewMessage(AFB_FATAL, "No Plugin=[%s] Url=%s", request->plugin, request->url);
         goto ExitOnError;
     }  
     
     // plugin callback did not return a valid Json Object
-    if (status != AFB_DONE) {
-        request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s]", request->api, request->plugin);
+    if (status == AFB_FAIL) {
+        request->jresp = jsonNewMessage(AFB_FATAL, "No API=[%s] for Plugin=[%s] url=[%s]", request->api, request->plugin, request->url);
         goto ExitOnError;
     }
-
-
-
     
     // Everything look OK
     return (status);
@@ -328,6 +274,7 @@ doPostIterate (void *cls, enum MHD_ValueKind kind, const char *key,
   AFB_request *request = (AFB_request*)postHandle->private;
   AFB_PostRequest postRequest;
   
+  fprintf (stderr, "postHandle key=%s filename=%s len=%d mime=%s\n", key, filename, size, mimetype);
    
   // Create and Item value for Plugin API
   item.kind     = kind;
@@ -347,7 +294,6 @@ doPostIterate (void *cls, enum MHD_ValueKind kind, const char *key,
   
   // effectively call plugin API                 
   status = findAndCallApi (request, &item);
-  
   // when returning no processing of postform stop
   if (status != AFB_SUCCESS) return MHD_NO;
   
@@ -376,12 +322,16 @@ STATIC AFB_request *createRequest (struct MHD_Connection *connection, AFB_sessio
     baseurl = strsep(&urlcpy2, "/");
     if (baseurl == NULL) {
         request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
+        request->errcode = MHD_HTTP_BAD_REQUEST;
+        goto Done;
     }
 
     // let's compute URL and call API
     baseapi = strsep(&urlcpy2, "/");
     if (baseapi == NULL) {
-        request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call url=[%s]", url);
+        request->jresp = jsonNewMessage(AFB_FATAL, "Invalid API call plugin=[%s] url=[%s]", baseurl, url);
+        request->errcode = MHD_HTTP_BAD_REQUEST;
+        goto Done;
     }
     
     // build request structure
@@ -391,7 +341,8 @@ STATIC AFB_request *createRequest (struct MHD_Connection *connection, AFB_sessio
     request->plugin = strdup (baseurl);
     request->api    = strdup (baseapi);
     request->plugins= session->plugins;
-    
+
+Done:    
     free(urlcpy1);
     return (request);
 }
@@ -422,20 +373,24 @@ PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, co
             // allocate application POST processor handle to zero
             postHandle = calloc(1, sizeof (AFB_PostHandle));
             postHandle->uid = postcount++; // build a UID for DEBUG
-            *con_cls = postHandle;         // attache POST handle to current HTTP request
+            *con_cls = postHandle;  // update context with posthandle
             
             // Let make sure we have the right encoding and a valid length
             encoding = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE);
+            
+            // We are facing an empty post let's process it as a get
+            if (encoding == NULL) {
+                request= createRequest (connection, session, url);
+                goto ProcessApiCall;
+            }
         
             // Form post is handle through a PostProcessor and call API once per form key
             if (strcasestr(encoding, FORM_CONTENT) != NULL) {
                 if (verbose) fprintf(stderr, "Create PostForm[uid=%d]\n", postHandle->uid);
 
                 request = createRequest (connection, session, url);
-                if (request->jresp != NULL) {
-                    errMessage = request->jresp;
-                    goto ExitOnError;
-                }
+                if (request->jresp != NULL) goto ProcessApiCall;
+
                 postHandle = malloc(sizeof (AFB_PostHandle)); // allocate application POST processor handle
                 postHandle->type   = AFB_POST_FORM;
                 postHandle->pp     = MHD_create_post_processor (connection, MAX_POST_SIZE, doPostIterate, postHandle);
@@ -465,14 +420,13 @@ PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, co
                 postHandle->type = AFB_POST_JSON;
                 postHandle->private = malloc(contentlen + 1); // allocate memory for full POST data + 1 for '\0' enf of string
 
-                if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen);
+                // if (verbose) fprintf(stderr, "Create PostJson[uid=%d] Size=%d\n", postHandle->uid, contentlen);
                 return MHD_YES;
 
             } else {
                 // We only support Json and Form Post format
                 errMessage = jsonNewMessage(AFB_FATAL, "Post Date wrong type encoding=%s != %s", encoding, JSON_CONTENT);
-                goto ExitOnError;
-                
+                goto ExitOnError;                
             }   
         }
 
@@ -482,18 +436,18 @@ PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, co
         if (*upload_data_size) {
     
             if (postHandle->type == AFB_POST_FORM) {
-                if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid);
+                // if (verbose) fprintf(stderr, "Processing PostForm[uid=%d]\n", postHandle->uid);
                 MHD_post_process (postHandle->pp, upload_data, *upload_data_size);
             }
             
             // Process JsonPost request when buffer is completed let's call API    
             if (postHandle->type == AFB_POST_JSON) {
-                if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid);
-
+                // if (verbose) fprintf(stderr, "Updating PostJson[uid=%d]\n", postHandle->uid);
                 memcpy(&postHandle->private[postHandle->len], upload_data, *upload_data_size);
                 postHandle->len = postHandle->len + *upload_data_size;
-                *upload_data_size = 0;
             }
+            
+            *upload_data_size = 0;
             return MHD_YES;
             
         } else {  // we have finish with Post reception let's finish the work
@@ -504,10 +458,16 @@ PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, co
                 errMessage = request->jresp;
                 goto ExitOnError;
             }
-
+            
+            // Postform add application context handle to request
+            if (postHandle->type == AFB_POST_FORM) {
+               postRequest.data = (char*) postHandle;
+               postRequest.type = postHandle->type;
+               request->post = &postRequest;
+            }
             
             if (postHandle->type == AFB_POST_JSON) {
-                if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid);
+                // if (verbose) fprintf(stderr, "Processing PostJson[uid=%d]\n", postHandle->uid);
 
                 param = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_LENGTH);
                 if (param) sscanf(param, "%i", &contentlen);
@@ -524,18 +484,18 @@ PUBLIC int doRestApi(struct MHD_Connection *connection, AFB_session *session, co
                 postRequest.type = postHandle->type;
                 request->post = &postRequest;
 
-                if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data);
+                // if (verbose) fprintf(stderr, "Close Post[%d] Buffer=%s\n", postHandle->uid, request->post->data);
             }
         }
     } else {
         // this is a get we only need a request
         request= createRequest (connection, session, url);
     };
-    
+
+ProcessApiCall:    
     // Request is ready let's call API without any extra handle
     status = findAndCallApi (request, NULL);
-    
-ExitOnResponse:
+
     serialized = json_object_to_json_string(request->jresp);
     webResponse = MHD_create_response_from_buffer(strlen(serialized), (void*) serialized, MHD_RESPMEM_MUST_COPY);
     
@@ -608,18 +568,58 @@ STATIC AFB_plugin ** RegisterJsonPlugins(AFB_plugin **plugins) {
 }
 
 void initPlugins(AFB_session *session) {
-    static AFB_plugin * plugins[10];
+    static AFB_plugin **plugins;
+    AFB_plugin* (*pluginRegisterFct)(void);
+    void *plugin;
+    char *pluginPath;
+    struct dirent *pluginDir;
+    DIR *dir;
     afbJsonType = json_object_new_string (AFB_MSG_JTYPE);
-    int i = 0;
-
-    plugins[i++] = tokenRegister(session),
-    plugins[i++] = alsaRegister(session),
-    plugins[i++] = helloWorldRegister(session),
-#ifdef HAVE_RADIO_PLUGIN
-    plugins[i++] = radioRegister(session),
-#endif
-    plugins[i++] = NULL;
-    
+    int num = 0;
+
+    /* pre-allocate for 20 plugins, we will downsize if necessary */
+    plugins = (AFB_plugin **) malloc (20*sizeof(AFB_plugin));
+
+    if ((dir = opendir(session->config->plugins)) == NULL) {
+        fprintf(stderr, "Could not open plugin directory [%s], exiting...\n", session->config->plugins);
+        exit (-1);
+    }
+
+    while ((pluginDir = readdir(dir)) != NULL) {
+
+        if (!strstr (pluginDir->d_name, ".so"))
+            continue;
+
+        asprintf (&pluginPath, "%s/%s", session->config->plugins, pluginDir->d_name);
+        plugin = dlopen (pluginPath, RTLD_NOW | RTLD_LOCAL);
+        pluginRegisterFct = dlsym (plugin, "pluginRegister");
+        free (pluginPath);
+        if (!plugin) {
+            if (verbose) fprintf(stderr, "[%s] is not loadable, continuing...\n", pluginDir->d_name);
+            continue;
+        } else if (!pluginRegisterFct) {
+            if (verbose) fprintf(stderr, "[%s] is not an AFB plugin, continuing...\n", pluginDir->d_name);
+            continue;
+        }
+
+        if (verbose) fprintf(stderr, "[%s] is a valid AFB plugin, loading it\n", pluginDir->d_name);
+        plugins[num] = (AFB_plugin *) malloc (sizeof(AFB_plugin));
+        plugins[num] = (**pluginRegisterFct)();
+        num++;
+        /* only 20 plugins are supported at that time */
+        if (num == 20) break;
+    }
+    plugins = (AFB_plugin **) realloc (plugins, (num+1)*sizeof(AFB_plugin));
+    plugins[num] = NULL;
+
+    closedir (dir);
+
+    if (plugins[0] == NULL) {
+        fprintf(stderr, "No plugins found, afb-daemon is unlikely to work in this configuration, exiting...\n");
+        exit (-1);
+    }
+
     // complete plugins and save them within current sessions    
     session->plugins = RegisterJsonPlugins(plugins);
+    session->pluginCount = num;
 }