json_echo.c (11560B)
1 /* SPDX-License-Identifier: LGPL-2.1-or-later OR (GPL-2.0-or-later WITH eCos-exception-2.0) */ 2 /* 3 This file is part of GNU libmicrohttpd. 4 Copyright (C) 2025 Christian Grothoff, Evgeny Grin (and other 5 contributing authors) 6 7 GNU libmicrohttpd is free software; you can redistribute it and/or 8 modify it under the terms of the GNU Lesser General Public 9 License as published by the Free Software Foundation; either 10 version 2.1 of the License, or (at your option) any later version. 11 12 GNU libmicrohttpd is distributed in the hope that it will be useful, 13 but WITHOUT ANY WARRANTY; without even the implied warranty of 14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 15 Lesser General Public License for more details. 16 17 Alternatively, you can redistribute GNU libmicrohttpd and/or 18 modify it under the terms of the GNU General Public License as 19 published by the Free Software Foundation; either version 2 of 20 the License, or (at your option) any later version, together 21 with the eCos exception, as follows: 22 23 As a special exception, if other files instantiate templates or 24 use macros or inline functions from this file, or you compile this 25 file and link it with other works to produce a work based on this 26 file, this file does not by itself cause the resulting work to be 27 covered by the GNU General Public License. However the source code 28 for this file must still be made available in accordance with 29 section (3) of the GNU General Public License v2. 30 31 This exception does not invalidate any other reasons why a work 32 based on this file might be covered by the GNU General Public 33 License. 34 35 You should have received copies of the GNU Lesser General Public 36 License and the GNU General Public License along with this library; 37 if not, see <https://www.gnu.org/licenses/>. 38 */ 39 /** 40 * @file json_echo.c 41 * @brief example for processing POST requests with JSON uploads, echos the JSON back to the client 42 * @author Christian Grothoff 43 * @author Karlson2k (Evgeny Grin) 44 */ 45 #include <sys/types.h> 46 #include <stdint.h> 47 #include <stdlib.h> 48 #include <string.h> 49 #include <stdio.h> 50 #include <errno.h> 51 #if ! defined(_WIN32) || defined (__CYGWIN__) 52 # include <sys/select.h> 53 # include <unistd.h> 54 #else 55 # include <conio.h> 56 #endif 57 #include <assert.h> 58 struct AppSockContext; /* Forward declaration */ 59 #define MHD_APP_SOCKET_CNTX_TYPE struct AppSockContext 60 #include <microhttpd2.h> 61 #include <jansson.h> 62 63 /** 64 * Bad request page. 65 */ 66 #define BAD_REQUEST_PAGE \ 67 "<html><head><title>Illegal request</title></head><body>Go away.</body></html>" 68 69 /** 70 * Invalid JSON page. 71 */ 72 #define FILE_NOT_FOUND_PAGE \ 73 "<html><head><title>Not found</title></head><body>Go away.</body></html>" 74 75 /** 76 * We keep the sockets we are waiting on in a DLL. 77 */ 78 struct AppSockContext 79 { 80 struct AppSockContext *next; 81 struct AppSockContext *prev; 82 struct MHD_EventUpdateContext *ecb_cntx; 83 MHD_Socket fd; 84 }; 85 86 87 /** 88 * Current read set. 89 */ 90 static fd_set rs; 91 92 /** 93 * Current write set. 94 */ 95 static fd_set ws; 96 97 /** 98 * Current error set. 99 */ 100 static fd_set es; 101 102 /** 103 * Maximum FD in any set. 104 */ 105 static MHD_Socket max_fd = 0; 106 107 /** 108 * Head of our internal list of sockets to select() on. 109 */ 110 static struct AppSockContext *head; 111 112 /** 113 * Generates 404. 114 */ 115 static struct MHD_Response *file_not_found_response; 116 117 /** 118 * Generates 400. 119 */ 120 static struct MHD_Response *bad_request_response; 121 122 123 static const struct MHD_UploadAction * 124 handle_upload (void *upload_cls, 125 struct MHD_Request *request, 126 size_t content_data_size, 127 void *content_data) 128 { 129 json_t *j; 130 json_error_t err; 131 char *s; 132 struct MHD_Response *response; 133 (void) upload_cls; /* Unused. Mute compiler warning. */ 134 135 j = json_loadb ((char *) content_data, 136 content_data_size, 137 0, 138 &err); 139 if (NULL == j) 140 return MHD_upload_action_from_response (request, 141 bad_request_response); 142 s = json_dumps (j, 143 JSON_INDENT (2)); 144 json_decref (j); 145 response = MHD_response_from_buffer (MHD_HTTP_STATUS_OK, 146 strlen (s), 147 s, 148 &free, 149 s); 150 return MHD_upload_action_from_response (request, 151 response); 152 } 153 154 155 static const struct MHD_Action * 156 handle_request (void *cls, 157 struct MHD_Request *request, 158 const struct MHD_String *path, 159 enum MHD_HTTP_Method method, 160 uint_fast64_t upload_size) 161 { 162 (void) cls; /* Unused. Mute compiler warning. */ 163 (void) path; /* Unused. Mute compiler warning. */ 164 165 if (method != MHD_HTTP_METHOD_POST) 166 return MHD_action_from_response (request, 167 file_not_found_response); 168 if (upload_size > 16 * 1024 * 1024) 169 return MHD_action_abort_request (request); 170 return MHD_action_process_upload_full (request, 171 upload_size, 172 &handle_upload, 173 NULL); 174 } 175 176 177 /* This is the function MHD will call when the external event 178 loop needs to change how it watches out for changes to 179 some socket's state */ 180 static MHD_APP_SOCKET_CNTX_TYPE * 181 sock_reg_update_cb ( 182 void *cls, 183 MHD_Socket fd, 184 enum MHD_FdState watch_for, 185 MHD_APP_SOCKET_CNTX_TYPE *app_cntx, 186 struct MHD_EventUpdateContext *ecb_cntx) 187 { 188 (void) cls; /* Unused. Mute compiler warning. */ 189 #ifdef MHD_SOCKETS_KIND_POSIX 190 /* The value is limited by MHD_D_OPTION_FD_NUMBER_LIMIT() */ 191 assert (fd < FD_SETSIZE); 192 #endif /* MHD_SOCKETS_KIND_POSIX */ 193 if (MHD_FD_STATE_NONE == watch_for) 194 { 195 /* Remove from DLL */ 196 if (app_cntx == head) 197 head = app_cntx->next; 198 if (NULL != app_cntx->prev) 199 app_cntx->prev->next = app_cntx->next; 200 if (NULL != app_cntx->next) 201 app_cntx->next->prev = app_cntx->prev; 202 free (app_cntx); 203 return NULL; 204 } 205 if (NULL == app_cntx) 206 { 207 /* First time, allocate data structure to keep 208 the socket and MHD's context */ 209 app_cntx = 210 (MHD_APP_SOCKET_CNTX_TYPE *) malloc (sizeof (MHD_APP_SOCKET_CNTX_TYPE)); 211 if (NULL == app_cntx) 212 return NULL; /* closes connection */ 213 /* prepend to DLL */ 214 app_cntx->prev = NULL; 215 app_cntx->next = head; 216 if (NULL != head) 217 head->prev = app_cntx; 218 head = app_cntx; 219 app_cntx->fd = fd; 220 } 221 else 222 { 223 /* socket must not change */ 224 assert (fd == app_cntx->fd); 225 } 226 /* MHD could change its associated context, so always update */ 227 app_cntx->ecb_cntx = ecb_cntx; 228 /* Since we are called by MHD in every iteration, we simply build 229 the event sets for select() here directly. */ 230 if (watch_for & MHD_FD_STATE_RECV) 231 FD_SET (fd, 232 &rs); 233 if (watch_for & MHD_FD_STATE_SEND) 234 FD_SET (fd, 235 &ws); 236 if (watch_for & MHD_FD_STATE_EXCEPT) 237 FD_SET (fd, 238 &es); 239 if (fd > max_fd) 240 max_fd = fd; 241 return app_cntx; 242 } 243 244 245 /** 246 * Mark the given response as HTML for the browser. 247 * 248 * @param response response to mark 249 */ 250 static void 251 mark_as_html (struct MHD_Response *response) 252 { 253 if (NULL == response) 254 return; 255 (void) MHD_response_add_header (response, 256 MHD_HTTP_HEADER_CONTENT_TYPE, 257 "text/html"); 258 } 259 260 261 /** 262 * Call with the port number as the only argument. 263 * Terminates when reading from stdin or on signals, such as CTRL-C. 264 */ 265 int 266 main (int argc, 267 char *const *argv) 268 { 269 struct MHD_Daemon *d; 270 unsigned int port; 271 char dummy; 272 273 if ( (argc != 2) || 274 (1 != sscanf (argv[1], 275 "%u%c", 276 &port, 277 &dummy)) || 278 (UINT16_MAX < port) ) 279 { 280 if (2 == argc) 281 { 282 fprintf (stderr, 283 "Usage: %s PORT\n", 284 argv[0]); 285 return 1; 286 } 287 port = 8080; 288 } 289 file_not_found_response = 290 MHD_response_from_buffer_static ( 291 MHD_HTTP_STATUS_NOT_FOUND, 292 strlen (FILE_NOT_FOUND_PAGE), 293 FILE_NOT_FOUND_PAGE); 294 mark_as_html (file_not_found_response); 295 if (MHD_SC_OK != 296 MHD_response_set_option (file_not_found_response, 297 &MHD_R_OPTION_REUSABLE (MHD_YES))) 298 return 1; 299 bad_request_response = 300 MHD_response_from_buffer_static ( 301 MHD_HTTP_STATUS_BAD_REQUEST, 302 strlen (BAD_REQUEST_PAGE), 303 BAD_REQUEST_PAGE); 304 mark_as_html (bad_request_response); 305 if (MHD_SC_OK != 306 MHD_response_set_option (bad_request_response, 307 &MHD_R_OPTION_REUSABLE (MHD_YES))) 308 return 1; 309 310 d = MHD_daemon_create (&handle_request, 311 NULL); 312 if (NULL == d) 313 return 1; 314 if (MHD_SC_OK != 315 MHD_DAEMON_SET_OPTIONS ( 316 d, 317 MHD_D_OPTION_REREGISTER_ALL (MHD_YES), 318 MHD_D_OPTION_WM_EXTERNAL_EVENT_LOOP_CB_LEVEL ( 319 &sock_reg_update_cb, 320 NULL), 321 MHD_D_OPTION_FD_NUMBER_LIMIT (FD_SETSIZE), 322 MHD_D_OPTION_DEFAULT_TIMEOUT (120 /* seconds */), 323 MHD_D_OPTION_CONN_MEMORY_LIMIT (256 * 1024), 324 MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, 325 (uint_least16_t) port))) 326 return 1; 327 if (MHD_SC_OK != 328 MHD_daemon_start (d)) 329 { 330 MHD_daemon_destroy (d); 331 return 1; 332 } 333 while (1) 334 { 335 struct timeval ts; 336 struct AppSockContext *pos; 337 uint_fast64_t next_wait; 338 339 FD_ZERO (&rs); 340 FD_ZERO (&ws); 341 FD_ZERO (&es); 342 #ifdef MHD_SOCKETS_KIND_POSIX 343 FD_SET (STDIN_FILENO, 344 &rs); 345 max_fd = STDIN_FILENO; 346 #endif /* MHD_SOCKETS_KIND_POSIX */ 347 348 /* This will cause MHD to call the #sock_reg_update_cb() */ 349 MHD_daemon_process_reg_events (d, 350 &next_wait); 351 #ifdef MHD_SOCKETS_KIND_POSIX 352 ts.tv_sec = (time_t) (next_wait / 1000000); 353 #else /* W32 */ 354 /* W32 cannot monitor "stdin" with select(). 355 Use poor man replacement. */ 356 if (300000u < next_wait) 357 next_wait = 300000u; 358 ts.tv_sec = (long) (next_wait / 1000000); 359 #endif /* W32 */ 360 ts.tv_usec = (long) (next_wait % 1000000); 361 /* Real applications may do nicer error handling here */ 362 (void) select ((int) max_fd + 1, 363 &rs, 364 &ws, 365 &es, 366 &ts); 367 #ifdef MHD_SOCKETS_KIND_POSIX 368 if (FD_ISSET (STDIN_FILENO, 369 &rs)) 370 break; /* exit on input on stdin */ 371 #else /* W32 */ 372 if (0 != _kbhit ()) 373 break; /* exit on console input */ 374 #endif /* W32 */ 375 376 /* Now we need to tell MHD which events were triggered */ 377 for (pos = head; NULL != pos; pos = pos->next) 378 { 379 enum MHD_FdState current_state = MHD_FD_STATE_NONE; 380 381 if (FD_ISSET (pos->fd, 382 &rs)) 383 current_state = 384 (enum MHD_FdState) (current_state | MHD_FD_STATE_RECV); 385 if (FD_ISSET (pos->fd, 386 &ws)) 387 current_state = 388 (enum MHD_FdState) (current_state | MHD_FD_STATE_SEND); 389 if (FD_ISSET (pos->fd, 390 &es)) 391 current_state = 392 (enum MHD_FdState) (current_state | MHD_FD_STATE_EXCEPT); 393 MHD_daemon_event_update (d, 394 pos->ecb_cntx, 395 current_state); 396 } 397 } 398 MHD_daemon_destroy (d); 399 MHD_response_destroy (file_not_found_response); 400 MHD_response_destroy (bad_request_response); 401 return 0; 402 }