json_echo.c (10299B)
1 /* 2 This file is part of libmicrohttpd 3 Copyright (C) 2025 Christian Grothoff (and other contributing authors) 4 5 This library is free software; you can redistribute it and/or 6 modify it under the terms of the GNU Lesser General Public 7 License as published by the Free Software Foundation; either 8 version 2.1 of the License, or (at your option) any later version. 9 10 This library 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 GNU 13 Lesser General Public License for more details. 14 15 You should have received a copy of the GNU Lesser General Public 16 License along with this library; if not, write to the Free Software 17 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 18 */ 19 /** 20 * @file json_echo.c 21 * @brief example for processing POST requests with JSON uploads, echos the JSON back to the client 22 * @author Christian Grothoff 23 */ 24 #include <stdlib.h> 25 #include <string.h> 26 #include <stdio.h> 27 #include <errno.h> 28 #include <time.h> 29 #include <microhttpd.h> 30 #include <jansson.h> 31 32 /** 33 * Bad request page. 34 */ 35 #define BAD_REQUEST_ERROR \ 36 "<html><head><title>Illegal request</title></head><body>Go away.</body></html>" 37 38 /** 39 * Invalid JSON page. 40 */ 41 #define NOT_FOUND_ERROR \ 42 "<html><head><title>Not found</title></head><body>Go away.</body></html>" 43 44 45 /** 46 * State we keep for each request. 47 */ 48 struct Request 49 { 50 51 /** 52 * Number of bytes received. 53 */ 54 size_t off; 55 56 /** 57 * Size of @a buf. 58 */ 59 size_t len; 60 61 /** 62 * Buffer for POST data. 63 */ 64 void *buf; 65 66 }; 67 68 69 /** 70 * Handler used to generate a 404 reply. 71 * 72 * @param connection connection to use 73 */ 74 static enum MHD_Result 75 not_found_page (struct MHD_Connection *connection) 76 { 77 struct MHD_Response *response; 78 enum MHD_Result ret; 79 80 response = 81 MHD_create_response_from_buffer_static (strlen (NOT_FOUND_ERROR), 82 (const void *) NOT_FOUND_ERROR); 83 if (NULL == response) 84 return MHD_NO; 85 ret = MHD_queue_response (connection, 86 MHD_HTTP_NOT_FOUND, 87 response); 88 if (MHD_YES != 89 MHD_add_response_header (response, 90 MHD_HTTP_HEADER_CONTENT_ENCODING, 91 "text/html")) 92 { 93 fprintf (stderr, 94 "Failed to set content encoding header!\n"); 95 } 96 MHD_destroy_response (response); 97 return ret; 98 } 99 100 101 /** 102 * Handler used to generate a 400 reply. 103 * 104 * @param connection connection to use 105 */ 106 static enum MHD_Result 107 invalid_request (struct MHD_Connection *connection) 108 { 109 enum MHD_Result ret; 110 struct MHD_Response *response; 111 112 response = 113 MHD_create_response_from_buffer_static ( 114 strlen (BAD_REQUEST_ERROR), 115 (const void *) BAD_REQUEST_ERROR); 116 if (NULL == response) 117 return MHD_NO; 118 ret = MHD_queue_response (connection, 119 MHD_HTTP_BAD_REQUEST, 120 response); 121 if (MHD_YES != 122 MHD_add_response_header (response, 123 MHD_HTTP_HEADER_CONTENT_ENCODING, 124 "text/html")) 125 { 126 fprintf (stderr, 127 "Failed to set content encoding header!\n"); 128 } 129 MHD_destroy_response (response); 130 return ret; 131 } 132 133 134 /** 135 * Main MHD callback for handling requests. 136 * 137 * @param cls argument given together with the function 138 * pointer when the handler was registered with MHD 139 * @param connection handle identifying the incoming connection 140 * @param url the requested url 141 * @param method the HTTP method used ("GET", "PUT", etc.) 142 * @param version the HTTP version string (i.e. "HTTP/1.1") 143 * @param upload_data the data being uploaded (excluding HEADERS, 144 * for a POST that fits into memory and that is encoded 145 * with a supported encoding, the POST data will NOT be 146 * given in upload_data and is instead available as 147 * part of MHD_get_connection_values; very large POST 148 * data *will* be made available incrementally in 149 * upload_data) 150 * @param upload_data_size set initially to the size of the 151 * upload_data provided; the method must update this 152 * value to the number of bytes NOT processed; 153 * @param req_cls pointer that the callback can set to some 154 * address and that will be preserved by MHD for future 155 * calls for this request; since the access handler may 156 * be called many times (i.e., for a PUT/POST operation 157 * with plenty of upload data) this allows the application 158 * to easily associate some request-specific state. 159 * If necessary, this state can be cleaned up in the 160 * global "MHD_RequestCompleted" callback (which 161 * can be set with the MHD_OPTION_NOTIFY_COMPLETED). 162 * Initially, <tt>*req_cls</tt> will be NULL. 163 * @return MHS_YES if the connection was handled successfully, 164 * MHS_NO if the socket must be closed due to a serious 165 * error while handling the request 166 */ 167 static enum MHD_Result 168 create_response (void *cls, 169 struct MHD_Connection *connection, 170 const char *url, 171 const char *method, 172 const char *version, 173 const char *upload_data, 174 size_t *upload_data_size, 175 void **req_cls) 176 { 177 struct Request *request = *req_cls; 178 struct MHD_Response *response; 179 enum MHD_Result ret; 180 unsigned int i; 181 182 (void) cls; /* Unused. Silence compiler warning. */ 183 (void) version; /* Unused. Silence compiler warning. */ 184 185 if (NULL == request) 186 { 187 const char *clen; 188 char dummy; 189 unsigned int len; 190 191 request = calloc (1, sizeof (struct Request)); 192 if (NULL == request) 193 { 194 fprintf (stderr, 195 "calloc error: %s\n", 196 strerror (errno)); 197 return MHD_NO; 198 } 199 *req_cls = request; 200 if (0 != strcmp (method, 201 MHD_HTTP_METHOD_POST)) 202 { 203 return not_found_page (connection); 204 } 205 clen = MHD_lookup_connection_value ( 206 connection, 207 MHD_HEADER_KIND, 208 MHD_HTTP_HEADER_CONTENT_LENGTH); 209 if (NULL == clen) 210 return invalid_request (connection); 211 if (1 != sscanf (clen, 212 "%u%c", 213 &len, 214 &dummy)) 215 return invalid_request (connection); 216 request->len = len; 217 request->buf = malloc (request->len); 218 if (NULL == request->buf) 219 return MHD_NO; 220 return MHD_YES; 221 } 222 if (0 != *upload_data_size) 223 { 224 if (request->len < *upload_data_size + request->off) 225 { 226 fprintf (stderr, 227 "Content-length header wrong, aborting\n"); 228 return MHD_NO; 229 } 230 memcpy (request->buf, 231 upload_data, 232 *upload_data_size); 233 request->off += *upload_data_size; 234 *upload_data_size = 0; 235 return MHD_YES; 236 } 237 { 238 json_t *j; 239 json_error_t err; 240 char *s; 241 242 j = json_loadb (request->buf, 243 request->len, 244 0, 245 &err); 246 if (NULL == j) 247 return invalid_request (connection); 248 s = json_dumps (j, 249 JSON_INDENT (2)); 250 json_decref (j); 251 response = 252 MHD_create_response_from_buffer (strlen (s), 253 s, 254 MHD_RESPMEM_MUST_FREE); 255 ret = MHD_queue_response (connection, 256 MHD_HTTP_OK, 257 response); 258 MHD_destroy_response (response); 259 return ret; 260 } 261 } 262 263 264 /** 265 * Callback called upon completion of a request. 266 * Decrements session reference counter. 267 * 268 * @param cls not used 269 * @param connection connection that completed 270 * @param req_cls session handle 271 * @param toe status code 272 */ 273 static void 274 request_completed_callback (void *cls, 275 struct MHD_Connection *connection, 276 void **req_cls, 277 enum MHD_RequestTerminationCode toe) 278 { 279 struct Request *request = *req_cls; 280 (void) cls; /* Unused. Silence compiler warning. */ 281 (void) connection; /* Unused. Silence compiler warning. */ 282 (void) toe; /* Unused. Silence compiler warning. */ 283 284 if (NULL == request) 285 return; 286 if (NULL != request->buf) 287 free (request->buf); 288 free (request); 289 } 290 291 292 /** 293 * Call with the port number as the only argument. 294 * Never terminates (other than by signals, such as CTRL-C). 295 */ 296 int 297 main (int argc, char *const *argv) 298 { 299 struct MHD_Daemon *d; 300 struct timeval tv; 301 struct timeval *tvp; 302 fd_set rs; 303 fd_set ws; 304 fd_set es; 305 MHD_socket max; 306 uint64_t mhd_timeout; 307 int port; 308 309 if (argc != 2) 310 { 311 printf ("%s PORT\n", argv[0]); 312 return 1; 313 } 314 port = atoi (argv[1]); 315 if ( (1 > port) || (port > 65535) ) 316 { 317 fprintf (stderr, 318 "Port must be a number between 1 and 65535.\n"); 319 return 1; 320 } 321 /* initialize PRNG */ 322 srand ((unsigned int) time (NULL)); 323 d = MHD_start_daemon (MHD_USE_ERROR_LOG, 324 (uint16_t) port, 325 NULL, NULL, 326 &create_response, NULL, 327 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, 328 MHD_OPTION_NOTIFY_COMPLETED, 329 &request_completed_callback, NULL, 330 MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE, 331 MHD_OPTION_END); 332 if (NULL == d) 333 return 1; 334 while (1) 335 { 336 max = 0; 337 FD_ZERO (&rs); 338 FD_ZERO (&ws); 339 FD_ZERO (&es); 340 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 341 break; /* fatal internal error */ 342 if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES) 343 { 344 #if ! defined(_WIN32) || defined(__CYGWIN__) 345 tv.tv_sec = (time_t) (mhd_timeout / 1000LL); 346 #else /* Native W32 */ 347 tv.tv_sec = (long) (mhd_timeout / 1000LL); 348 #endif /* Native W32 */ 349 tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000; 350 tvp = &tv; 351 } 352 else 353 tvp = NULL; 354 if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp)) 355 { 356 if (EINTR != errno) 357 abort (); 358 } 359 MHD_run (d); 360 } 361 MHD_stop_daemon (d); 362 return 0; 363 }