refactoring (in progress, tbf)
[src/app-framework-binder.git] / src / http-svc.c
index 99c4142..706abbc 100644 (file)
 #include "../include/local-def.h"
 #include "afb-method.h"
 #include "afb-hreq.h"
+#include "afb-websock.h"
+#include "afb-apis.h"
+#include "session.h"
+#include "afb-req-itf.h"
 
+#define JSON_CONTENT  "application/json"
+#define FORM_CONTENT  MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA
 
 struct afb_hsrv_handler {
        struct afb_hsrv_handler *next;
        const char *prefix;
        size_t length;
-       int (*handler) (struct afb_hreq *, struct afb_hreq_post *, void *);
+       int (*handler) (struct afb_hreq *, void *);
        void *data;
        int priority;
 };
@@ -42,15 +48,14 @@ struct afb_diralias {
        int dirfd;
 };
 
-int afb_request_one_page_api_redirect(
-               struct afb_hreq *request,
-               struct afb_hreq_post *post,
+int afb_hreq_one_page_api_redirect(
+               struct afb_hreq *hreq,
                void *data)
 {
        size_t plen;
        char *url;
 
-       if (request->lentail >= 2 && request->tail[1] == '#')
+       if (hreq->lentail >= 2 && hreq->tail[1] == '#')
                return 0;
        /*
         * Here we have for example:
@@ -61,19 +66,19 @@ int afb_request_one_page_api_redirect(
         *
         * Let compute plen that include the / at end (for "/pre/")
         */
-       plen = request->lenurl - request->lentail + 1;
-       url = alloca(request->lenurl + 3);
-       memcpy(url, request->url, plen);
+       plen = hreq->lenurl - hreq->lentail + 1;
+       url = alloca(hreq->lenurl + 3);
+       memcpy(url, hreq->url, plen);
        url[plen++] = '#';
        url[plen++] = '!';
-       memcpy(&url[plen], &request->tail[1], request->lentail);
-       return afb_hreq_redirect_to(request, url);
+       memcpy(&url[plen], &hreq->tail[1], hreq->lentail);
+       return afb_hreq_redirect_to(hreq, url);
 }
 
-struct afb_hsrv_handler *afb_hsrv_handler_new(
+static struct afb_hsrv_handler *new_handler(
                struct afb_hsrv_handler *head,
                const char *prefix,
-               int (*handler) (struct afb_hreq *, struct afb_hreq_post *, void *),
+               int (*handler) (struct afb_hreq *, void *),
                void *data,
                int priority)
 {
@@ -111,46 +116,95 @@ struct afb_hsrv_handler *afb_hsrv_handler_new(
        return head;
 }
 
-int afb_req_add_handler(
+int afb_hsrv_add_handler(
                AFB_session * session,
                const char *prefix,
-               int (*handler) (struct afb_hreq *, struct afb_hreq_post *, void *),
+               int (*handler) (struct afb_hreq *, void *),
                void *data,
                int priority)
 {
        struct afb_hsrv_handler *head;
 
-       head = afb_hsrv_handler_new(session->handlers, prefix, handler, data, priority);
+       head = new_handler(session->handlers, prefix, handler, data, priority);
        if (head == NULL)
                return 0;
        session->handlers = head;
        return 1;
 }
 
-static int relay_to_doRestApi(struct afb_hreq *request, struct afb_hreq_post *post, void *data)
+static const char uuid_header[] = "x-afb-uuid";
+static const char uuid_arg[] = "uuid";
+static const char uuid_cookie[] = "uuid";
+
+static struct AFB_clientCtx *afb_hreq_context(struct afb_hreq *hreq)
+{
+       const char *uuid;
+
+       if (hreq->context == NULL) {
+               uuid = afb_hreq_get_header(hreq, uuid_header);
+               if (uuid == NULL)
+                       uuid = afb_hreq_get_argument(hreq, uuid_arg);
+               if (uuid == NULL)
+                       uuid = afb_hreq_get_cookie(hreq, uuid_cookie);
+               hreq->context = _ctxClientGet(uuid);
+       }
+       return hreq->context;
+}
+
+static int afb_hreq_websocket_switch(struct afb_hreq *hreq, void *data)
+{
+       int later;
+
+       afb_hreq_context(hreq);
+       if (hreq->lentail != 0 || !afb_websock_check(hreq, &later))
+               return 0;
+
+       if (!later) {
+               struct afb_websock *ws = afb_websock_create(hreq->connection);
+               if (ws == NULL) {
+                       /* TODO */
+               } else {
+                       /* TODO */
+               }
+       }
+       return 1;
+}
+
+static int afb_hreq_rest_api(struct afb_hreq *hreq, void *data)
 {
-       return doRestApi(request->connection, request->session, &request->tail[1], get_method_name(request->method),
-                        post->upload_data, post->upload_data_size, (void **)request->recorder);
+       const char *api, *verb;
+       size_t lenapi, lenverb;
+
+       api = &hreq->tail[strspn(hreq->tail, "/")];
+       lenapi = strcspn(api, "/");
+       verb = &api[lenapi];
+       verb = &verb[strspn(verb, "/")];
+       lenverb = strcspn(verb, "/");
+
+       if (!(*api && *verb && lenapi && lenverb))
+               return 0;
+
+       return afb_apis_handle(afb_hreq_to_req(hreq), api, lenapi, verb, lenverb);
 }
 
-static int handle_alias(struct afb_hreq *request, struct afb_hreq_post *post, void *data)
+static int handle_alias(struct afb_hreq *hreq, void *data)
 {
        struct afb_diralias *da = data;
 
-       if (request->method != afb_method_get) {
-               afb_hreq_reply_error(request, MHD_HTTP_METHOD_NOT_ALLOWED);
+       if (hreq->method != afb_method_get) {
+               afb_hreq_reply_error(hreq, MHD_HTTP_METHOD_NOT_ALLOWED);
                return 1;
        }
 
-       if (!afb_hreq_valid_tail(request)) {
-               afb_hreq_reply_error(request, MHD_HTTP_FORBIDDEN);
+       if (!afb_hreq_valid_tail(hreq)) {
+               afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
                return 1;
        }
 
-       return afb_hreq_reply_file(request, da->dirfd, &request->tail[1]);
+       return afb_hreq_reply_file(hreq, da->dirfd, &hreq->tail[1]);
 }
 
-int afb_req_add_alias(AFB_session * session, const char *prefix, const char *alias, int priority)
+int afb_hsrv_add_alias(AFB_session * session, const char *prefix, const char *alias, int priority)
 {
        struct afb_diralias *da;
        int dirfd;
@@ -166,7 +220,7 @@ int afb_req_add_alias(AFB_session * session, const char *prefix, const char *ali
                da->directory = alias;
                da->lendir = strlen(da->directory);
                da->dirfd = dirfd;
-               if (afb_req_add_handler(session, prefix, handle_alias, (void *)alias, priority))
+               if (afb_hsrv_add_handler(session, prefix, handle_alias, da, priority))
                        return 1;
                free(da);
        }
@@ -174,25 +228,39 @@ int afb_req_add_alias(AFB_session * session, const char *prefix, const char *ali
        return 0;
 }
 
-static int my_default_init(AFB_session * session)
+void afb_hsrv_reply_error(struct MHD_Connection *connection, unsigned int status)
 {
-       int idx;
-
-       if (!afb_req_add_handler(session, session->config->rootapi, relay_to_doRestApi, NULL, 1))
-               return 0;
-
-       for (idx = 0; session->config->aliasdir[idx].url != NULL; idx++)
-               if (!afb_req_add_alias
-                   (session, session->config->aliasdir[idx].url, session->config->aliasdir[idx].path, 0))
-                       return 0;
-
-       if (!afb_req_add_alias(session, "", session->config->rootdir, -10))
-               return 0;
-
-       if (!afb_req_add_handler(session, session->config->rootbase, afb_request_one_page_api_redirect, NULL, -20))
-               return 0;
+       char *buffer;
+       int length;
+       struct MHD_Response *response;
+
+       length = asprintf(&buffer, "<html><body>error %u</body></html>", status);
+       if (length > 0)
+               response = MHD_create_response_from_buffer((unsigned)length, buffer, MHD_RESPMEM_MUST_FREE);
+       else {
+               buffer = "<html><body>error</body></html>";
+               response = MHD_create_response_from_buffer(strlen(buffer), buffer, MHD_RESPMEM_PERSISTENT);
+       }
+       if (!MHD_queue_response(connection, status, response))
+               fprintf(stderr, "Failed to reply error code %u", status);
+       MHD_destroy_response(response);
+}
 
-       return 1;
+static int postproc(void *cls,
+                    enum MHD_ValueKind kind,
+                    const char *key,
+                    const char *filename,
+                    const char *content_type,
+                    const char *transfer_encoding,
+                    const char *data,
+                   uint64_t off,
+                   size_t size)
+{
+       struct afb_hreq *hreq = cls;
+       if (filename != NULL)
+               return afb_hreq_post_add_file(hreq, key, filename, data, size);
+       else
+               return afb_hreq_post_add(hreq, key, data, size);
 }
 
 static int access_handler(
@@ -202,82 +270,119 @@ static int access_handler(
                const char *methodstr,
                const char *version,
                const char *upload_data,
-               size_t * upload_data_size,
-               void **recorder)
+               size_t *upload_data_size,
+               void **recordreq)
 {
-       struct afb_hreq_post post;
-       struct afb_hreq request;
+       int rc;
+       struct afb_hreq *hreq;
        enum afb_method method;
        AFB_session *session;
        struct afb_hsrv_handler *iter;
+       const char *type;
 
        session = cls;
-       post.upload_data = upload_data;
-       post.upload_data_size = upload_data_size;
-
-#if 0
-       struct afb_hreq *previous;
-
-       previous = *recorder;
-       if (previous) {
-               assert((void **)previous->recorder == recorder);
-               assert(previous->session == session);
-               assert(previous->connection == connection);
-               assert(previous->method == get_method(methodstr));
-               assert(previous->url == url);
-
-               /* TODO */
-/*
-               assert(previous->post_handler != NULL);
-               previous->post_handler(previous, &post);
-               return MHD_NO;
-*/
+       hreq = *recordreq;
+       if (hreq == NULL) {
+               /* create the request */
+               hreq = calloc(1, sizeof *hreq);
+               if (hreq == NULL)
+                       goto internal_error;
+               *recordreq = hreq;
+
+               /* get the method */
+               method = get_method(methodstr);
+               method &= afb_method_get | afb_method_post;
+               if (method == afb_method_none)
+                       goto bad_request;
+
+               /* init the request */
+               hreq->session = cls;
+               hreq->connection = connection;
+               hreq->method = method;
+               hreq->version = version;
+               hreq->tail = hreq->url = url;
+               hreq->lentail = hreq->lenurl = strlen(url);
+
+               /* init the post processing */
+               if (method == afb_method_post) {
+                       type = afb_hreq_get_header(hreq, MHD_HTTP_HEADER_CONTENT_TYPE);
+                       if (type == NULL) {
+                               /* an empty post, let's process it as a get */
+                               hreq->method = afb_method_get;
+                       } else if (strcasestr(type, FORM_CONTENT) != NULL) {
+                               hreq->postform = MHD_create_post_processor (connection, 65500, postproc, hreq);
+                               if (hreq->postform == NULL)
+                                       goto internal_error;
+                       } else if (strcasestr(type, JSON_CONTENT) == NULL) {
+                               afb_hsrv_reply_error(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE);
+                               return MHD_YES;
+                       }
+               }
        }
-#endif
 
-       method = get_method(methodstr);
-       if (method == afb_method_none) {
-               afb_hreq_reply_error(&request, MHD_HTTP_BAD_REQUEST);
-               return MHD_YES;
+       /* process further data */
+       if (*upload_data_size) {
+               if (hreq->postform != NULL) {
+                       if (!MHD_post_process (hreq->postform, upload_data, *upload_data_size))
+                               goto internal_error;
+               } else {
+                       if (!afb_hreq_post_add(hreq, NULL, upload_data, *upload_data_size))
+                               goto internal_error;
+               }
+               *upload_data_size = 0;
+               return MHD_YES;         
        }
 
-       /* init the request */
-       request.session = cls;
-       request.connection = connection;
-       request.method = method;
-       request.tail = request.url = url;
-       request.lentail = request.lenurl = strlen(url);
-       request.recorder = (struct afb_hreq **)recorder;
-       request.post_handler = NULL;
-       request.post_completed = NULL;
-       request.post_data = NULL;
+       /* flush the data */
+       afb_hreq_post_end(hreq);
+       if (hreq->postform != NULL) {
+               rc = MHD_destroy_post_processor(hreq->postform);
+               hreq->postform = NULL;
+               if (rc == MHD_NO)
+                       goto bad_request;
+       }
 
        /* search an handler for the request */
        iter = session->handlers;
        while (iter) {
-               if (afb_hreq_unprefix(&request, iter->prefix, iter->length)) {
-                       if (iter->handler(&request, &post, iter->data))
+               if (afb_hreq_unprefix(hreq, iter->prefix, iter->length)) {
+                       if (iter->handler(hreq, iter->data))
                                return MHD_YES;
-                       request.tail = request.url;
-                       request.lentail = request.lenurl;
+                       hreq->tail = hreq->url;
+                       hreq->lentail = hreq->lenurl;
                }
                iter = iter->next;
        }
 
        /* no handler */
-       afb_hreq_reply_error(&request, method != afb_method_get ? MHD_HTTP_BAD_REQUEST : MHD_HTTP_NOT_FOUND);
+       afb_hreq_reply_error(hreq, MHD_HTTP_NOT_FOUND);
+       return MHD_YES;
+
+bad_request:
+       afb_hsrv_reply_error(connection, MHD_HTTP_BAD_REQUEST);
+       return MHD_YES;
+
+internal_error:
+       afb_hsrv_reply_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR);
        return MHD_YES;
 }
 
 /* Because of POST call multiple time requestApi we need to free POST handle here */
-static void end_handler(void *cls, struct MHD_Connection *connection, void **con_cls,
+static void end_handler(void *cls, struct MHD_Connection *connection, void **recordreq,
                        enum MHD_RequestTerminationCode toe)
 {
-       AFB_PostHandle *posthandle = *con_cls;
+       AFB_session *session;
+       struct afb_hreq *hreq;
 
-       /* if post handle was used let's free everything */
-       if (posthandle != NULL)
-               endPostRequest(posthandle);
+       session = cls;
+       hreq = *recordreq;
+
+       if (hreq != NULL) {
+               if (hreq->postform != NULL)
+                       MHD_destroy_post_processor(hreq->postform);
+               afb_hreq_drop_data(hreq);
+               free(hreq);
+       }
 }
 
 static int new_client_handler(void *cls, const struct sockaddr *addr, socklen_t addrlen)
@@ -306,28 +411,45 @@ static int init_lib_magic (AFB_session *session)
        /* Warning: should not use NULL for DB [libmagic bug wont pass efence check] */
        if (magic_load(session->magic, MAGIC_DB) != 0) {
                fprintf(stderr,"cannot load magic database - %s\n", magic_error(session->magic));
-/*
                magic_close(session->magic);
                session->magic = NULL;
                return 0;
-*/
        }
 
        return 1;
 }
 #endif
 
-AFB_error httpdStart(AFB_session * session)
+static int my_default_init(AFB_session * session)
 {
+       int idx;
 
+       if (!afb_hsrv_add_handler(session, session->config->rootapi, afb_hreq_websocket_switch, NULL, 20))
+               return 0;
+
+       if (!afb_hsrv_add_handler(session, session->config->rootapi, afb_hreq_rest_api, NULL, 10))
+               return 0;
+
+       for (idx = 0; session->config->aliasdir[idx].url != NULL; idx++)
+               if (!afb_hsrv_add_alias (session, session->config->aliasdir[idx].url, session->config->aliasdir[idx].path, 0))
+                       return 0;
+
+       if (!afb_hsrv_add_alias(session, "", session->config->rootdir, -10))
+               return 0;
+
+       if (!afb_hsrv_add_handler(session, session->config->rootbase, afb_hreq_one_page_api_redirect, NULL, -20))
+               return 0;
+
+       return 1;
+}
+
+AFB_error httpdStart(AFB_session * session)
+{
        if (!my_default_init(session)) {
                printf("Error: initialisation of httpd failed");
                return AFB_FATAL;
        }
 
-       /* Initialise Client Session Hash Table */
-       ctxStoreInit(CTX_NBCLIENTS);
-
 #if defined(USE_MAGIC_MIME_TYPE)
        /*TBD open libmagic cache [fail to pass EFENCE check (allocating 0 bytes)] */
        init_lib_magic (session);
@@ -339,7 +461,7 @@ AFB_error httpdStart(AFB_session * session)
        }
 
        session->httpd = MHD_start_daemon(
-               MHD_USE_EPOLL_LINUX_ONLY | MHD_USE_TCP_FASTOPEN | MHD_USE_DEBUG,
+               MHD_USE_EPOLL_LINUX_ONLY | MHD_USE_TCP_FASTOPEN | MHD_USE_DEBUG | MHD_USE_SUSPEND_RESUME,
                (uint16_t) session->config->httpdPort,  /* port */
                new_client_handler, NULL,       /* Tcp Accept call back + extra attribute */
                access_handler, session,        /* Http Request Call back + extra attribute */