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>
35 #include "../include/local-def.h"
37 // proto missing from GCC
38 char *strcasestr(const char *haystack, const char *needle);
40 static int rqtcount = 0; // dummy request rqtcount to make each message be different
41 static int postcount = 0;
42 static int aipUrlLen=0; // do not compute apiurl for each call
43 static int baseUrlLen=0; // do not compute baseurl for each call
45 // Because of POST call multiple time requestApi we need to free POST handle here
46 static void endRequest (void *cls, struct MHD_Connection *connection, void **con_cls, enum MHD_RequestTerminationCode toe) {
47 AFB_HttpPost *posthandle = *con_cls;
49 // if post handle was used let's free everything
51 if (verbose) fprintf (stderr, "End Post Request UID=%d\n", posthandle->uid);
52 free (posthandle->data);
58 // Create check etag value
59 STATIC void computeEtag(char *etag, int maxlen, struct stat *sbuf) {
61 time = sbuf->st_mtim.tv_sec;
62 snprintf(etag, maxlen, "%d", time);
65 STATIC int servFile (struct MHD_Connection *connection, AFB_session *session, const char *url, char *filepath, int fd) {
66 const char *etagCache;
68 struct MHD_Response *response;
72 if (fstat (fd, &sbuf) != 0) {
73 fprintf(stderr, "Fail to stat file: [%s] error:%s\n", filepath, strerror(errno));
77 // if url is a directory let's add index.html and redirect client
78 if (S_ISDIR (sbuf.st_mode)) {
79 strncpy (filepath, url, sizeof (filepath));
81 if (url [strlen (url) -1] != '/') strncat (filepath, "/", sizeof (filepath));
82 strncat (filepath, "index.html", sizeof (filepath));
84 response = MHD_create_response_from_buffer (0,"", MHD_RESPMEM_PERSISTENT);
85 MHD_add_response_header (response,MHD_HTTP_HEADER_LOCATION, filepath);
86 ret = MHD_queue_response (connection, MHD_HTTP_MOVED_PERMANENTLY, response);
88 } else if (! S_ISREG (sbuf.st_mode)) { // only standard file any other one including symbolic links are refused.
90 fprintf (stderr, "Fail file: [%s] is not a regular file\n", filepath);
91 const char *errorstr = "<html><body>Alsa-Json-Gateway Invalid file type</body></html>";
92 response = MHD_create_response_from_buffer (strlen (errorstr),
93 (void *) errorstr, MHD_RESPMEM_PERSISTENT);
94 ret = MHD_queue_response (connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
98 // https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=fr
99 // ftp://ftp.heanet.ie/disk1/www.gnu.org/software/libmicrohttpd/doxygen/dc/d0c/microhttpd_8h.html
101 // Check etag value and load file only when modification date changes
102 etagCache = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_NONE_MATCH);
103 computeEtag(etagValue, sizeof (etagValue), &sbuf);
105 if (etagCache != NULL && strcmp(etagValue, etagCache) == 0) {
106 close(fd); // file did not change since last upload
107 if (verbose) fprintf(stderr, "Not Modify: [%s]\n", filepath);
108 response = MHD_create_response_from_buffer(0, "", MHD_RESPMEM_PERSISTENT);
109 MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
110 MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
111 ret = MHD_queue_response(connection, MHD_HTTP_NOT_MODIFIED, response);
113 } else { // it's a new file, we need to upload it to client
114 if (verbose) fprintf(stderr, "Serving: [%s]\n", filepath);
115 response = MHD_create_response_from_fd(sbuf.st_size, fd);
116 MHD_add_response_header(response, MHD_HTTP_HEADER_CACHE_CONTROL, session->cacheTimeout); // default one hour cache
117 MHD_add_response_header(response, MHD_HTTP_HEADER_ETAG, etagValue);
118 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
121 MHD_destroy_response(response);
126 // minimal httpd file server for static HTML,JS,CSS,etc...
127 STATIC int requestFile(struct MHD_Connection *connection, AFB_session *session, const char* url) {
133 // build full path from rootdir + url
134 strncpy(filepath, session->config->rootdir, sizeof (filepath));
135 strncat(filepath, url, 511);
137 // try to open file and get its size
138 if (-1 == (fd = open(filepath, O_RDONLY))) {
139 fprintf(stderr, "Fail to open file: [%s] error:%s\n", filepath, strerror(errno));
143 // open file is OK let use it
144 ret = servFile (connection, session, url, filepath, fd);
148 // this function return either Index.htlm or a redirect to /#!route to make angular happy
149 STATIC int checkHTML5(struct MHD_Connection *connection, AFB_session *session, const char* url) {
153 struct MHD_Response *response;
156 // if requesting '/' serve index.html
157 if (strlen (url) == 0) {
158 strncpy(filepath, session->config->rootdir, sizeof (filepath));
159 strncat(filepath, "/index.html", sizeof (filepath));
160 // try to open file and get its size
161 if (-1 == (fd = open(filepath, O_RDONLY))) {
162 fprintf(stderr, "Fail to open file: [%s] error:%s\n", filepath, strerror(errno));
163 // Nothing respond to this request Files, API, Angular Base
164 const char *errorstr = "<html><body>Alsa-Json-Gateway Unknown or Not readable file</body></html>";
165 response = MHD_create_response_from_buffer(strlen(errorstr),(void *)errorstr, MHD_RESPMEM_PERSISTENT);
166 ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
170 ret = servFile (connection, session, url, filepath, fd);
175 // we are facing a internal route within the HTML5 OnePageApp let's redirect ex: /myapp/#!user/login
176 strncpy(filepath, session->config->rootbase, sizeof (filepath));
177 strncat(filepath, "#!", sizeof (filepath));
178 strncat(filepath, url, sizeof (filepath));
179 response = MHD_create_response_from_buffer(session->config->html5.len,(void *)session->config->html5.msg, MHD_RESPMEM_PERSISTENT);
180 MHD_add_response_header (response, "Location", "http://somesite.com/page.html");
181 MHD_queue_response (connection, MHD_HTTP_OK, response);
184 // Check and Dispatch HTTP request
185 STATIC int newRequest(void *cls,
186 struct MHD_Connection *connection,
190 const char *upload_data, size_t *upload_data_size, void **con_cls) {
192 AFB_session *session = cls;
193 struct MHD_Response *response;
196 // this is an Angular request we change URL /!#xxxxx
197 if (0 == strncmp(url, session->config->rootapi, baseUrlLen)) {
198 ret = doRestApi(connection, session, method, &url[baseUrlLen]);
202 // From here only accept get request
203 if (0 != strcmp(method, MHD_HTTP_METHOD_GET)) return MHD_NO; /* unexpected method */
205 // If a static file exist serve it now
206 ret = requestFile(connection, session, url);
207 if (ret != FAILED) return ret;
209 // no static was served let check for Angular redirect
210 if (0 == strncmp(url, session->config->rootbase, baseUrlLen)) {
211 ret = checkHTML5(connection, session, &url[baseUrlLen]);
215 // Nothing respond to this request Files, API, Angular Base
216 const char *errorstr = "<html><body>Alsa-Json-Gateway Unknown or Not readable file</body></html>";
217 response = MHD_create_response_from_buffer(strlen(errorstr), (void*)errorstr, MHD_RESPMEM_PERSISTENT);
218 ret = MHD_queue_response(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, response);
222 STATIC int newClient(void *cls, const struct sockaddr * addr, socklen_t addrlen) {
223 // check if client is comming from an acceptable IP
224 return (MHD_YES); // MHD_NO
228 PUBLIC AFB_ERROR httpdStart(AFB_session *session) {
231 aipUrlLen = strlen (session->config->rootapi);
232 baseUrlLen = strlen (session->config->rootbase);
235 printf("AFB:notice Waiting port=%d rootdir=%s\n", session->config->httpdPort, session->config->rootdir);
236 printf("AFB:notice Browser URL= http://localhost:%d\n", session->config->httpdPort);
239 session->httpd = (void*) MHD_start_daemon(
240 MHD_USE_SELECT_INTERNALLY | MHD_USE_DEBUG, // use request and not threads
241 session->config->httpdPort, // port
242 &newClient, NULL, // Tcp Accept call back + extra attribute
243 &newRequest, session, // Http Request Call back + extra attribute
244 MHD_OPTION_NOTIFY_COMPLETED, &endRequest, NULL,
245 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, MHD_OPTION_END); // 15s + options-end
246 // TBD: MHD_OPTION_SOCK_ADDR
248 if (session->httpd == NULL) {
249 printf("Error: httpStart invalid httpd port: %d", session->config->httpdPort);
256 PUBLIC AFB_ERROR httpdLoop(AFB_session *session) {
257 static int count = 0;
259 if (verbose) fprintf(stderr, "AFB:notice entering httpd waiting loop\n");
260 if (session->foreground) {
263 fprintf(stderr, "AFB:notice Use Ctrl-C to quit");
269 if (verbose) fprintf(stderr, "AFB:notice httpd alive [%d]\n", count++);
273 // should never return from here
277 PUBLIC int httpdStatus(AFB_session *session) {
278 return (MHD_run(session->httpd));
281 PUBLIC void httpdStop(AFB_session *session) {
282 MHD_stop_daemon(session->httpd);