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 * Handle standard HTTP request
19 * Features/Restriction:
20 - handle ETAG to limit upload to modified/new files [cache default 3600s]
21 - handles redirect to index.htlm when path is a directory [code 301]
22 - only support GET method
23 - does not follow link.
25 References: https://www.gnu.org/software/libmicrohttpd/manual/html_node/index.html#Top
26 http://libmicrohttpd.sourcearchive.com/documentation/0.4.2/microhttpd_8h.html
27 https://gnunet.org/svn/libmicrohttpd/src/examples/fileserver_example_external_select.c
28 https://github.com/json-c/json-c
29 POST https://www.gnu.org/software/libmicrohttpd/manual/html_node/microhttpd_002dpost.html#microhttpd_002dpost
33 #include <microhttpd.h>
36 #include "../include/local-def.h"
38 // let's compute fixed URL length only once
42 // proto missing from GCC
43 char *strcasestr(const char *haystack, const char *needle);
45 static int rqtcount = 0; // dummy request rqtcount to make each message be different
46 static int postcount = 0;
48 // try to open libmagic to handle mime types
49 static AFB_error initLibMagic (AFB_session *session) {
50 const char *magic_full;
52 /*MAGIC_MIME tells magic to return a mime of the file, but you can specify different things*/
53 if (verbose) printf("Loading mimetype default magic database\n");
55 session->magic = magic_open(MAGIC_MIME);
56 if (session->magic == NULL) {
57 fprintf(stderr,"ERROR: unable to initialize magic library\n");
60 if (magic_load(session->magic, NULL) != 0) {
61 fprintf(stderr,"cannot load magic database - %s\n", magic_error(session->magic));
62 magic_close(session->magic);
67 //magic_full = magic_file(magic_cookie, actual_file);
68 //printf("%s\n", magic_full);
72 // Because of POST call multiple time requestApi we need to free POST handle here
73 static void endRequest (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
74 AFB_HttpPost *posthandle = *con_cls;
76 // if post handle was used let's free everything
78 if (verbose) fprintf (stderr, "End Post Request UID=%d\n", posthandle->uid);
79 free (posthandle->data);
85 // Create check etag value
86 STATIC void computeEtag(char *etag, int maxlen, struct stat *sbuf) {
88 time = sbuf->st_mtim.tv_sec;
89 snprintf(etag, maxlen, "%d", time);
92 STATIC int servFile (struct MHD_Connection *connection, AFB_session *session, const char *url, AFB_staticfile *staticfile) {
93 const char *etagCache, *mimetype;
95 struct MHD_Response *response;
99 if (fstat (staticfile->fd, &sbuf) != 0) {
100 fprintf(stderr, "Fail to stat file: [%s] error:%s\n", staticfile->path, strerror(errno));
104 // if url is a directory let's add index.html and redirect client
105 if (S_ISDIR (sbuf.st_mode)) {
106 if (url [strlen (url) -1] != '/') strncat (staticfile->path, "/", sizeof (staticfile->path));
107 strncat (staticfile->path, "index.html", sizeof (staticfile->path));
108 close (staticfile->fd); // close directory try to open index.html
109 if (-1 == (staticfile->fd = open(staticfile->path, O_RDONLY)) || (fstat (staticfile->fd, &sbuf) != 0)) {
110 fprintf(stderr, "No Index.html in direcory [%s]\n", staticfile->path);
114 } else if (! S_ISREG (sbuf.st_mode)) { // only standard file any other one including symbolic links are refused.
116 fprintf (stderr, "Fail file: [%s] is not a regular file\n", staticfile->path);
117 const char *errorstr = "<html><body>Alsa-Json-Gateway Invalid file type</body></html>";
118 response = MHD_create_response_from_buffer (strlen (errorstr),
119 (void *) errorstr, MHD_RESPMEM_PERSISTENT);
120 MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
125 // https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=fr
126 // ftp://ftp.heanet.ie/disk1/www.gnu.org/software/libmicrohttpd/doxygen/dc/d0c/microhttpd_8h.html
128 // Check etag value and load file only when modification date changes
129 etagCache = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
130 computeEtag(etagValue, sizeof (etagValue), &sbuf);
132 if (etagCache != NULL && strcmp(etagValue, etagCache) == 0) {
133 close(staticfile->fd); // file did not change since last upload
134 if (verbose) fprintf(stderr, "Not Modify: [%s]\n", staticfile->path);
135 response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
136 MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
137 MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
138 MHD_queue_response(connection, MHD_HTTP_NOT_MODIFIED, response);
140 } else { // it's a new file, we need to upload it to client
141 // if we have magic let's try to guest mime type
142 if (session->magic) {
144 mimetype= magic_descriptor(session->magic, staticfile->fd);
145 if (mimetype != NULL) MHD_add_response_header (response, MHD_HTTP_HEADER_CONTENT_TYPE, mimetype);
147 if (verbose) fprintf(stderr, "Serving: [%s] mine=%s\n", staticfile->path, mimetype);
148 response = MHD_create_response_from_fd(sbuf.st_size, staticfile->fd);
149 MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
150 MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
151 MHD_queue_response(connection, MHD_HTTP_OK, response);
155 MHD_destroy_response(response);
159 // minimal httpd file server for static HTML,JS,CSS,etc...
160 STATIC int requestFile(struct MHD_Connection *connection, AFB_session *session, const char* url) {
163 AFB_staticfile staticfile;
165 // build full path from rootdir + url
168 strncpy(staticfile.path, session->config->rootdir, sizeof (staticfile.path));
169 strncat(staticfile.path, url, sizeof (staticfile.path));
171 // try to open file and get its size
172 if (-1 == (staticfile.fd = open(staticfile.path, O_RDONLY))) {
173 fprintf(stderr, "Fail to open file: [%s] error:%s\n", staticfile.path, strerror(errno));
177 // open file is OK let use it
178 ret = servFile (connection, session, url, &staticfile);
182 // this function return either Index.htlm or a redirect to /#!route to make angular happy
183 STATIC int checkHTML5(struct MHD_Connection *connection, AFB_session *session, const char* url) {
187 struct MHD_Response *response;
188 AFB_staticfile staticfile;
190 // Any URL prefixed with /rootbase is served with index.html ex: /opa,/opa/,/opa/#!xxxxxx
191 if ( url[0] == '\0' || url[1] == '\0' || url[1] == '#') {
192 strncpy(staticfile.path, session->config->rootdir, sizeof (staticfile.path));
193 strncat(staticfile.path, "/index.html", sizeof (staticfile.path));
194 // try to open file and get its size
195 if (-1 == (staticfile.fd = open(staticfile.path, O_RDONLY))) {
196 fprintf(stderr, "Fail to open file: [%s] error:%s\n", staticfile.path, strerror(errno));
197 // Nothing respond to this request Files, API, Angular Base
198 const char *errorstr = "<html><body>Application Framework OPA/index.html Not found</body></html>";
199 response = MHD_create_response_from_buffer(strlen(errorstr),(void *)errorstr, MHD_RESPMEM_PERSISTENT);
200 MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
203 ret = servFile (connection, session, url, &staticfile);
208 // Url match /opa/xxxx but not /opa#!xxxx we redirect the URL to /opa#!/xxxx to force index.html reload
209 strncpy(staticfile.path, session->config->rootbase, sizeof (staticfile.path));
210 strncat(staticfile.path, "#!", sizeof (staticfile.path));
211 strncat(staticfile.path, url, sizeof (staticfile.path));
212 response = MHD_create_response_from_buffer(0,"", MHD_RESPMEM_PERSISTENT);
213 MHD_add_response_header (response, "Location", staticfile.path);
214 MHD_queue_response (connection, MHD_HTTP_MOVED_PERMANENTLY, response);
215 if (verbose) fprintf (stderr,"checkHTML5 redirect to [%s]\n",staticfile.path);
219 // Check and Dispatch HTTP request
220 STATIC int newRequest(void *cls,
221 struct MHD_Connection *connection,
225 const char *upload_data, size_t *upload_data_size, void **con_cls) {
227 AFB_session *session = cls;
228 struct MHD_Response *response;
231 // this is a REST API let's check for plugins
232 if (0 == strncmp(url, session->config->rootapi, apiUrlLen)) {
233 ret = doRestApi(connection, session, method, &url[apiUrlLen+1]);
237 // From here only accept get request
238 if (0 != strcmp(method, MHD_HTTP_METHOD_GET)) return MHD_NO; /* unexpected method */
240 // If a static file exist serve it now
241 ret = requestFile(connection, session, url);
242 if (ret != FAILED) return ret;
244 // no static was served let check for Angular redirect
245 if (0 == strncmp(url, session->config->rootbase, baseUrlLen)) {
246 ret = checkHTML5(connection, session, &url[baseUrlLen]);
250 // Nothing respond to this request Files, API, Angular Base
251 const char *errorstr = "<html><body>Alsa-Json-Gateway Unknown or Not readable file</body></html>";
252 response = MHD_create_response_from_buffer(strlen(errorstr), (void*)errorstr, MHD_RESPMEM_PERSISTENT);
253 ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
257 STATIC int newClient(void *cls, const struct sockaddr * addr, socklen_t addrlen) {
258 // check if client is coming from an acceptable IP
259 return (MHD_YES); // MHD_NO
263 PUBLIC AFB_error httpdStart(AFB_session *session) {
265 // compute fixed URL length at startup time
266 apiUrlLen = strlen (session->config->rootapi);
267 baseUrlLen= strlen (session->config->rootbase);
269 // open libmagic cache
270 initLibMagic (session);
274 printf("AFB:notice Waiting port=%d rootdir=%s\n", session->config->httpdPort, session->config->rootdir);
275 printf("AFB:notice Browser URL= http://localhost:%d\n", session->config->httpdPort);
278 session->httpd = (void*) MHD_start_daemon(
279 MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, // use request and not threads
280 session->config->httpdPort, // port
281 &newClient, NULL, // Tcp Accept call back + extra attribute
282 &newRequest, session, // Http Request Call back + extra attribute
283 MHD_OPTION_NOTIFY_COMPLETED, &endRequest, NULL,
284 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, MHD_OPTION_END); // 15s + options-end
285 // TBD: MHD_OPTION_SOCK_ADDR
287 if (session->httpd == NULL) {
288 printf("Error: httpStart invalid httpd port: %d", session->config->httpdPort);
295 PUBLIC AFB_error httpdLoop(AFB_session *session) {
296 static int count = 0;
298 if (verbose) fprintf(stderr, "AFB:notice entering httpd waiting loop\n");
299 if (session->foreground) {
302 fprintf(stderr, "AFB:notice Use Ctrl-C to quit");
308 if (verbose) fprintf(stderr, "AFB:notice httpd alive [%d]\n", count++);
312 // should never return from here
316 PUBLIC int httpdStatus(AFB_session *session) {
317 return (MHD_run(session->httpd));
320 PUBLIC void httpdStop(AFB_session *session) {
321 MHD_stop_daemon(session->httpd);