simplifications
[src/app-framework-binder.git] / src / afb-hsrv.c
1 /*
2  * Copyright 2016 IoT.bzh
3  * Author: José Bollo <jose.bollo@iot.bzh>
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *   http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17
18 #define _GNU_SOURCE
19
20 #include <stdio.h>
21 #include <string.h>
22 #include <assert.h>
23 #include <poll.h>
24 #include <fcntl.h>
25 #include <sys/stat.h>
26
27 #include <microhttpd.h>
28
29 #include "local-def.h"
30 #include "afb-method.h"
31 #include "afb-hreq.h"
32 #include "afb-hsrv.h"
33 #include "afb-websock.h"
34 #include "afb-apis.h"
35 #include "afb-req-itf.h"
36 #include "verbose.h"
37 #include "utils-upoll.h"
38
39 #define JSON_CONTENT  "application/json"
40 #define FORM_CONTENT  MHD_HTTP_POST_ENCODING_MULTIPART_FORMDATA
41
42
43 struct afb_hsrv_handler {
44         struct afb_hsrv_handler *next;
45         const char *prefix;
46         size_t length;
47         int (*handler) (struct afb_hreq *, void *);
48         void *data;
49         int priority;
50 };
51
52 struct afb_diralias {
53         const char *alias;
54         const char *directory;
55         size_t lendir;
56         int dirfd;
57 };
58
59 struct afb_hsrv {
60         unsigned refcount;
61         struct afb_hsrv_handler *handlers;
62         struct MHD_Daemon *httpd;
63         struct upoll *upoll;
64         char *cache_to;
65 };
66
67
68
69 static struct afb_hsrv_handler *new_handler(
70                 struct afb_hsrv_handler *head,
71                 const char *prefix,
72                 int (*handler) (struct afb_hreq *, void *),
73                 void *data,
74                 int priority)
75 {
76         struct afb_hsrv_handler *link, *iter, *previous;
77         size_t length;
78
79         /* get the length of the prefix without its leading / */
80         length = strlen(prefix);
81         while (length && prefix[length - 1] == '/')
82                 length--;
83
84         /* allocates the new link */
85         link = malloc(sizeof *link);
86         if (link == NULL)
87                 return NULL;
88
89         /* initialize it */
90         link->prefix = prefix;
91         link->length = length;
92         link->handler = handler;
93         link->data = data;
94         link->priority = priority;
95
96         /* adds it */
97         previous = NULL;
98         iter = head;
99         while (iter && (priority < iter->priority || (priority == iter->priority && length <= iter->length))) {
100                 previous = iter;
101                 iter = iter->next;
102         }
103         link->next = iter;
104         if (previous == NULL)
105                 return link;
106         previous->next = link;
107         return head;
108 }
109
110 int afb_hsrv_add_handler(
111                 struct afb_hsrv *hsrv,
112                 const char *prefix,
113                 int (*handler) (struct afb_hreq *, void *),
114                 void *data,
115                 int priority)
116 {
117         struct afb_hsrv_handler *head;
118
119         head = new_handler(hsrv->handlers, prefix, handler, data, priority);
120         if (head == NULL)
121                 return 0;
122         hsrv->handlers = head;
123         return 1;
124 }
125
126 int afb_hreq_one_page_api_redirect(
127                 struct afb_hreq *hreq,
128                 void *data)
129 {
130         size_t plen;
131         char *url;
132
133         if (hreq->lentail >= 2 && hreq->tail[1] == '#')
134                 return 0;
135         /*
136          * Here we have for example:
137          *    url  = "/pre/dir/page"   lenurl = 13
138          *    tail =     "/dir/page"   lentail = 9
139          *
140          * We will produce "/pre/#!dir/page"
141          *
142          * Let compute plen that include the / at end (for "/pre/")
143          */
144         plen = hreq->lenurl - hreq->lentail + 1;
145         url = alloca(hreq->lenurl + 3);
146         memcpy(url, hreq->url, plen);
147         url[plen++] = '#';
148         url[plen++] = '!';
149         memcpy(&url[plen], &hreq->tail[1], hreq->lentail);
150         return afb_hreq_redirect_to(hreq, url);
151 }
152
153 static int afb_hreq_websocket_switch(struct afb_hreq *hreq, void *data)
154 {
155         int later;
156
157         afb_hreq_context(hreq);
158         if (hreq->lentail != 0 || !afb_websock_check(hreq, &later))
159                 return 0;
160
161         if (!later) {
162                 struct afb_websock *ws = afb_websock_create(hreq);
163                 if (ws != NULL)
164                         hreq->upgrade = 1;
165         }
166         return 1;
167 }
168
169 static int afb_hreq_rest_api(struct afb_hreq *hreq, void *data)
170 {
171         const char *api, *verb;
172         size_t lenapi, lenverb;
173         struct AFB_clientCtx *context;
174
175         api = &hreq->tail[strspn(hreq->tail, "/")];
176         lenapi = strcspn(api, "/");
177         verb = &api[lenapi];
178         verb = &verb[strspn(verb, "/")];
179         lenverb = strcspn(verb, "/");
180
181         if (!(*api && *verb && lenapi && lenverb))
182                 return 0;
183
184         context = afb_hreq_context(hreq);
185         return afb_apis_handle(afb_hreq_to_req(hreq), context, api, lenapi, verb, lenverb);
186 }
187
188 static int handle_alias(struct afb_hreq *hreq, void *data)
189 {
190         struct afb_diralias *da = data;
191
192         if (hreq->method != afb_method_get) {
193                 afb_hreq_reply_error(hreq, MHD_HTTP_METHOD_NOT_ALLOWED);
194                 return 1;
195         }
196
197         if (!afb_hreq_valid_tail(hreq)) {
198                 afb_hreq_reply_error(hreq, MHD_HTTP_FORBIDDEN);
199                 return 1;
200         }
201
202         return afb_hreq_reply_file(hreq, da->dirfd, &hreq->tail[1]);
203 }
204
205 int afb_hsrv_add_alias(struct afb_hsrv *hsrv, const char *prefix, const char *alias, int priority)
206 {
207         struct afb_diralias *da;
208         int dirfd;
209
210         dirfd = open(alias, O_PATH|O_DIRECTORY);
211         if (dirfd < 0) {
212                 /* TODO message */
213                 return 0;
214         }
215         da = malloc(sizeof *da);
216         if (da != NULL) {
217                 da->alias = prefix;
218                 da->directory = alias;
219                 da->lendir = strlen(da->directory);
220                 da->dirfd = dirfd;
221                 if (afb_hsrv_add_handler(hsrv, prefix, handle_alias, da, priority))
222                         return 1;
223                 free(da);
224         }
225         close(dirfd);
226         return 0;
227 }
228
229 static void reply_error(struct MHD_Connection *connection, unsigned int status)
230 {
231         char *buffer;
232         int length;
233         struct MHD_Response *response;
234
235         length = asprintf(&buffer, "<html><body>error %u</body></html>", status);
236         if (length > 0)
237                 response = MHD_create_response_from_buffer((unsigned)length, buffer, MHD_RESPMEM_MUST_FREE);
238         else {
239                 buffer = "<html><body>error</body></html>";
240                 response = MHD_create_response_from_buffer(strlen(buffer), buffer, MHD_RESPMEM_PERSISTENT);
241         }
242         if (!MHD_queue_response(connection, status, response))
243                 fprintf(stderr, "Failed to reply error code %u", status);
244         MHD_destroy_response(response);
245 }
246
247 static int postproc(void *cls,
248                     enum MHD_ValueKind kind,
249                     const char *key,
250                     const char *filename,
251                     const char *content_type,
252                     const char *transfer_encoding,
253                     const char *data,
254                     uint64_t off,
255                     size_t size)
256 {
257         struct afb_hreq *hreq = cls;
258         if (filename != NULL)
259                 return afb_hreq_post_add_file(hreq, key, filename, data, size);
260         else
261                 return afb_hreq_post_add(hreq, key, data, size);
262 }
263
264 static int access_handler(
265                 void *cls,
266                 struct MHD_Connection *connection,
267                 const char *url,
268                 const char *methodstr,
269                 const char *version,
270                 const char *upload_data,
271                 size_t *upload_data_size,
272                 void **recordreq)
273 {
274         int rc;
275         struct afb_hreq *hreq;
276         enum afb_method method;
277         struct afb_hsrv *hsrv;
278         struct afb_hsrv_handler *iter;
279         const char *type;
280
281         hsrv = cls;
282         hreq = *recordreq;
283         if (hreq == NULL) {
284                 /* create the request */
285                 hreq = calloc(1, sizeof *hreq);
286                 if (hreq == NULL)
287                         goto internal_error;
288                 *recordreq = hreq;
289
290                 /* get the method */
291                 method = get_method(methodstr);
292                 method &= afb_method_get | afb_method_post;
293                 if (method == afb_method_none)
294                         goto bad_request;
295
296                 /* init the request */
297                 hreq->cacheTimeout = hsrv->cache_to;
298                 hreq->connection = connection;
299                 hreq->method = method;
300                 hreq->version = version;
301                 hreq->tail = hreq->url = url;
302                 hreq->lentail = hreq->lenurl = strlen(url);
303
304                 /* init the post processing */
305                 if (method == afb_method_post) {
306                         type = afb_hreq_get_header(hreq, MHD_HTTP_HEADER_CONTENT_TYPE);
307                         if (type == NULL) {
308                                 /* an empty post, let's process it as a get */
309                                 hreq->method = afb_method_get;
310                         } else if (strcasestr(type, FORM_CONTENT) != NULL) {
311                                 hreq->postform = MHD_create_post_processor (connection, 65500, postproc, hreq);
312                                 if (hreq->postform == NULL)
313                                         goto internal_error;
314                                 return MHD_YES;
315                         } else if (strcasestr(type, JSON_CONTENT) == NULL) {
316                                 reply_error(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE);
317                                 return MHD_YES;
318                         }
319                 }
320         }
321
322         /* process further data */
323         if (*upload_data_size) {
324                 if (hreq->postform != NULL) {
325                         if (!MHD_post_process (hreq->postform, upload_data, *upload_data_size))
326                                 goto internal_error;
327                 } else {
328                         if (!afb_hreq_post_add(hreq, NULL, upload_data, *upload_data_size))
329                                 goto internal_error;
330                 }
331                 *upload_data_size = 0;
332                 return MHD_YES;         
333         }
334
335         /* flush the data */
336         if (hreq->postform != NULL) {
337                 rc = MHD_destroy_post_processor(hreq->postform);
338                 hreq->postform = NULL;
339                 if (rc == MHD_NO)
340                         goto bad_request;
341         }
342
343         /* search an handler for the request */
344         iter = hsrv->handlers;
345         while (iter) {
346                 if (afb_hreq_unprefix(hreq, iter->prefix, iter->length)) {
347                         if (iter->handler(hreq, iter->data))
348                                 return MHD_YES;
349                         hreq->tail = hreq->url;
350                         hreq->lentail = hreq->lenurl;
351                 }
352                 iter = iter->next;
353         }
354
355         /* no handler */
356         afb_hreq_reply_error(hreq, MHD_HTTP_NOT_FOUND);
357         return MHD_YES;
358
359 bad_request:
360         reply_error(connection, MHD_HTTP_BAD_REQUEST);
361         return MHD_YES;
362
363 internal_error:
364         reply_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR);
365         return MHD_YES;
366 }
367
368 /* Because of POST call multiple time requestApi we need to free POST handle here */
369 static void end_handler(void *cls, struct MHD_Connection *connection, void **recordreq,
370                         enum MHD_RequestTerminationCode toe)
371 {
372         struct afb_hreq *hreq;
373
374         hreq = *recordreq;
375         if (hreq->upgrade)
376                 MHD_suspend_connection (connection);
377         afb_hreq_free(hreq);
378 }
379
380 static int new_client_handler(void *cls, const struct sockaddr *addr, socklen_t addrlen)
381 {
382         return MHD_YES;
383 }
384
385 /* infinite loop */
386 static void hsrv_handle_event(struct MHD_Daemon *httpd)
387 {
388         MHD_run(httpd);
389 }
390
391 int afb_hsrv_set_cache_timeout(struct afb_hsrv *hsrv, int duration)
392 {
393         int rc;
394         char *dur;
395
396         rc = asprintf(&dur, "%d", duration);
397         if (rc < 0)
398                 return 0;
399
400         free(hsrv->cache_to);
401         hsrv->cache_to = dur;
402         return 1;
403 }
404
405 int _afb_hsrv_start(struct afb_hsrv *hsrv, uint16_t port, unsigned int connection_timeout)
406 {
407         struct upoll *upoll;
408         struct MHD_Daemon *httpd;
409         const union MHD_DaemonInfo *info;
410
411         httpd = MHD_start_daemon(
412                 MHD_USE_EPOLL_LINUX_ONLY | MHD_USE_TCP_FASTOPEN | MHD_USE_DEBUG | MHD_USE_SUSPEND_RESUME,
413                 port,                           /* port */
414                 new_client_handler, NULL,       /* Tcp Accept call back + extra attribute */
415                 access_handler, hsrv,   /* Http Request Call back + extra attribute */
416                 MHD_OPTION_NOTIFY_COMPLETED, end_handler, hsrv,
417                 MHD_OPTION_CONNECTION_TIMEOUT, connection_timeout,
418                 MHD_OPTION_END);        /* options-end */
419
420         if (httpd == NULL) {
421                 printf("Error: httpStart invalid httpd port: %d", (int)port);
422                 return 0;
423         }
424
425         info = MHD_get_daemon_info(httpd, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
426         if (info == NULL) {
427                 MHD_stop_daemon(httpd);
428                 fprintf(stderr, "Error: httpStart no pollfd");
429                 return 0;
430         }
431
432         upoll = upoll_open(info->listen_fd, httpd);
433         if (upoll == NULL) {
434                 MHD_stop_daemon(httpd);
435                 fprintf(stderr, "Error: connection to upoll of httpd failed");
436                 return 0;
437         }
438         upoll_on_readable(upoll, (void*)hsrv_handle_event);
439
440         hsrv->httpd = httpd;
441         hsrv->upoll = upoll;
442         return 1;
443 }
444
445 void _afb_hsrv_stop(struct afb_hsrv *hsrv)
446 {
447         if (hsrv->upoll)
448                 upoll_close(hsrv->upoll);
449         hsrv->upoll = NULL;
450         if (hsrv->httpd != NULL)
451                 MHD_stop_daemon(hsrv->httpd);
452         hsrv->httpd = NULL;
453 }
454
455 struct afb_hsrv *afb_hsrv_create()
456 {
457         struct afb_hsrv *result = calloc(1, sizeof(struct afb_hsrv));
458         if (result != NULL)
459                 result->refcount = 1;
460         return result;
461 }
462
463 void afb_hsrv_put(struct afb_hsrv *hsrv)
464 {
465         assert(hsrv->refcount != 0);
466         if (!--hsrv->refcount) {
467                 _afb_hsrv_stop(hsrv);
468                 free(hsrv);
469         }
470 }
471
472 static int my_default_init(struct afb_hsrv *hsrv, AFB_session * session)
473 {
474         int idx;
475
476         if (!afb_hsrv_add_handler(hsrv, session->config->rootapi, afb_hreq_websocket_switch, NULL, 20))
477                 return 0;
478
479         if (!afb_hsrv_add_handler(hsrv, session->config->rootapi, afb_hreq_rest_api, NULL, 10))
480                 return 0;
481
482         for (idx = 0; session->config->aliasdir[idx].url != NULL; idx++)
483                 if (!afb_hsrv_add_alias (hsrv, session->config->aliasdir[idx].url, session->config->aliasdir[idx].path, 0))
484                         return 0;
485
486         if (!afb_hsrv_add_alias(hsrv, "", session->config->rootdir, -10))
487                 return 0;
488
489         if (!afb_hsrv_add_handler(hsrv, session->config->rootbase, afb_hreq_one_page_api_redirect, NULL, -20))
490                 return 0;
491
492         return 1;
493 }
494
495 int afb_hsrv_start(AFB_session * session)
496 {
497         int rc;
498         struct afb_hsrv *hsrv;
499
500         hsrv = afb_hsrv_create();
501         if (hsrv == NULL) {
502                 fprintf(stderr, "memory allocation failure\n");
503                 return 0;
504         }
505
506         if (!afb_hsrv_set_cache_timeout(hsrv, session->config->cacheTimeout)
507         || !my_default_init(hsrv, session)) {
508                 printf("Error: initialisation of httpd failed");
509                 afb_hsrv_put(hsrv);
510                 return 0;
511         }
512
513         if (verbosity) {
514                 printf("AFB:notice Waiting port=%d rootdir=%s\n", session->config->httpdPort, session->config->rootdir);
515                 printf("AFB:notice Browser URL= http:/*localhost:%d\n", session->config->httpdPort);
516         }
517
518         rc = _afb_hsrv_start(hsrv, (uint16_t) session->config->httpdPort, 15);
519         if (!rc)
520                 return 0;
521
522         session->hsrv = hsrv;
523         return 1;
524 }
525
526 void afb_hsrv_stop(AFB_session * session)
527 {
528         _afb_hsrv_stop(session->hsrv);
529 }
530