json_echo.c (10293B)
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 181 (void) cls; /* Unused. Silence compiler warning. */ 182 (void) version; /* Unused. Silence compiler warning. */ 183 184 if (NULL == request) 185 { 186 const char *clen; 187 char dummy; 188 unsigned int len; 189 190 request = calloc (1, sizeof (struct Request)); 191 if (NULL == request) 192 { 193 fprintf (stderr, 194 "calloc error: %s\n", 195 strerror (errno)); 196 return MHD_NO; 197 } 198 *req_cls = request; 199 if (0 != strcmp (method, 200 MHD_HTTP_METHOD_POST)) 201 { 202 return not_found_page (connection); 203 } 204 clen = MHD_lookup_connection_value ( 205 connection, 206 MHD_HEADER_KIND, 207 MHD_HTTP_HEADER_CONTENT_LENGTH); 208 if (NULL == clen) 209 return invalid_request (connection); 210 if (1 != sscanf (clen, 211 "%u%c", 212 &len, 213 &dummy)) 214 return invalid_request (connection); 215 request->len = len; 216 request->buf = malloc (request->len); 217 if (NULL == request->buf) 218 return MHD_NO; 219 return MHD_YES; 220 } 221 if (0 != *upload_data_size) 222 { 223 if (request->len < *upload_data_size + request->off) 224 { 225 fprintf (stderr, 226 "Content-length header wrong, aborting\n"); 227 return MHD_NO; 228 } 229 memcpy (request->buf, 230 upload_data, 231 *upload_data_size); 232 request->off += *upload_data_size; 233 *upload_data_size = 0; 234 return MHD_YES; 235 } 236 { 237 json_t *j; 238 json_error_t err; 239 char *s; 240 241 j = json_loadb (request->buf, 242 request->len, 243 0, 244 &err); 245 if (NULL == j) 246 return invalid_request (connection); 247 s = json_dumps (j, 248 JSON_INDENT (2)); 249 json_decref (j); 250 response = 251 MHD_create_response_from_buffer (strlen (s), 252 s, 253 MHD_RESPMEM_MUST_FREE); 254 ret = MHD_queue_response (connection, 255 MHD_HTTP_OK, 256 response); 257 MHD_destroy_response (response); 258 return ret; 259 } 260 } 261 262 263 /** 264 * Callback called upon completion of a request. 265 * Decrements session reference counter. 266 * 267 * @param cls not used 268 * @param connection connection that completed 269 * @param req_cls session handle 270 * @param toe status code 271 */ 272 static void 273 request_completed_callback (void *cls, 274 struct MHD_Connection *connection, 275 void **req_cls, 276 enum MHD_RequestTerminationCode toe) 277 { 278 struct Request *request = *req_cls; 279 (void) cls; /* Unused. Silence compiler warning. */ 280 (void) connection; /* Unused. Silence compiler warning. */ 281 (void) toe; /* Unused. Silence compiler warning. */ 282 283 if (NULL == request) 284 return; 285 if (NULL != request->buf) 286 free (request->buf); 287 free (request); 288 } 289 290 291 /** 292 * Call with the port number as the only argument. 293 * Never terminates (other than by signals, such as CTRL-C). 294 */ 295 int 296 main (int argc, char *const *argv) 297 { 298 struct MHD_Daemon *d; 299 struct timeval tv; 300 struct timeval *tvp; 301 fd_set rs; 302 fd_set ws; 303 fd_set es; 304 MHD_socket max; 305 uint64_t mhd_timeout; 306 int port; 307 308 if (argc != 2) 309 { 310 printf ("%s PORT\n", argv[0]); 311 return 1; 312 } 313 port = atoi (argv[1]); 314 if ( (1 > port) || (port > 65535) ) 315 { 316 fprintf (stderr, 317 "Port must be a number between 1 and 65535.\n"); 318 return 1; 319 } 320 /* initialize PRNG */ 321 srand ((unsigned int) time (NULL)); 322 d = MHD_start_daemon (MHD_USE_ERROR_LOG, 323 (uint16_t) port, 324 NULL, NULL, 325 &create_response, NULL, 326 MHD_OPTION_CONNECTION_TIMEOUT, (unsigned int) 15, 327 MHD_OPTION_NOTIFY_COMPLETED, 328 &request_completed_callback, NULL, 329 MHD_OPTION_APP_FD_SETSIZE, (int) FD_SETSIZE, 330 MHD_OPTION_END); 331 if (NULL == d) 332 return 1; 333 while (1) 334 { 335 max = 0; 336 FD_ZERO (&rs); 337 FD_ZERO (&ws); 338 FD_ZERO (&es); 339 if (MHD_YES != MHD_get_fdset (d, &rs, &ws, &es, &max)) 340 break; /* fatal internal error */ 341 if (MHD_get_timeout64 (d, &mhd_timeout) == MHD_YES) 342 { 343 #if ! defined(_WIN32) || defined(__CYGWIN__) 344 tv.tv_sec = (time_t) (mhd_timeout / 1000LL); 345 #else /* Native W32 */ 346 tv.tv_sec = (long) (mhd_timeout / 1000LL); 347 #endif /* Native W32 */ 348 tv.tv_usec = ((long) (mhd_timeout % 1000)) * 1000; 349 tvp = &tv; 350 } 351 else 352 tvp = NULL; 353 if (-1 == select ((int) max + 1, &rs, &ws, &es, tvp)) 354 { 355 if (EINTR != errno) 356 abort (); 357 } 358 MHD_run (d); 359 } 360 MHD_stop_daemon (d); 361 return 0; 362 }