Recent

Author Topic: libmicrohttpd simple example  (Read 1518 times)

nomorelogic

  • Full Member
  • ***
  • Posts: 192
libmicrohttpd simple example
« on: May 02, 2025, 07:39:25 pm »
hello everyone
I am trying to understand how libmicrohttpd works in order to set up a restful json microservice.

There is probably something I am missing and I need help.
I enclose the source of the microservice.

Code: Pascal  [Select][+][-]
  1. program restfulhttpserverthreadsafe;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   cthreads,
  7.   Classes,
  8.   SysUtils,
  9.   cmem,
  10.   libmicrohttpd,
  11.   fpjson,
  12.   jsonparser,
  13.   syncobjs;
  14.  
  15. const
  16.   PORT = 8888;
  17.  
  18. var
  19.   ShouldStop: boolean = False;
  20.   Daemon: PMHD_Daemon;
  21.   StopLock: TCriticalSection;
  22.  
  23.  
  24.   function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  25.     url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;
  26.     con_cls: PPointer): cint; cdecl;
  27.   var
  28.     responseText: string;
  29.     response: PMHD_Response;
  30.     jsonObj: TJSONObject;
  31.     uploadStr: string;
  32.   begin
  33.     Result := 0;
  34.     if con_cls^ = nil then
  35.     begin
  36.       // first call -> init connection
  37.       con_cls^ := Pointer(1);
  38.       Exit(MHD_YES);
  39.     end;
  40.  
  41.  
  42.     if StrPas(url) = '/shutdown' then
  43.     begin
  44.       jsonObj := TJSONObject.Create;
  45.       jsonObj.Add('status', 'shutting down');
  46.       responseText := jsonObj.AsJSON;
  47.       jsonObj.Free;
  48.  
  49.       // try gracefully stop
  50.       StopLock.Enter;
  51.       try
  52.         ShouldStop := True;
  53.       finally
  54.         StopLock.Leave;
  55.       end;
  56.     end
  57.     else if (StrPas(url) = '/api/status') and (StrPas(method) = 'GET') then
  58.     begin
  59.       jsonObj := TJSONObject.Create;
  60.       jsonObj.Add('status', 'ok');
  61.       jsonObj.Add('uptime', FormatDateTime('hh:nn:ss', Now));
  62.       responseText := jsonObj.AsJSON;
  63.       jsonObj.Free;
  64.     end
  65.     else if (StrPas(url) = '/api/echo') and (StrPas(method) = 'POST') then
  66.     begin
  67.       // do we have a playload?
  68.       if (upload_data <> nil) and (upload_data_size^ > 0) then
  69.       begin
  70.         try
  71.           // prepare data
  72.           SetString(uploadStr, upload_data, upload_data_size^);
  73.           // "cast" as json
  74.           jsonObj := GetJSON(uploadStr) as TJSONObject;
  75.           responseText := jsonObj.AsJSON;
  76.           jsonObj.Free;
  77.         except
  78.           on E: Exception do
  79.           begin
  80.             // Se c'è un errore nel parsing, restituisco un errore JSON
  81.             responseText := '{"error": "Invalid JSON format"}';
  82.           end;
  83.         end;
  84.  
  85.       end
  86.       else
  87.       begin
  88.         // playload is empty?
  89.         responseText := '{"error": "No data received"}';
  90.       end;
  91.     end;
  92.  
  93.     response := MHD_create_response_from_buffer(Length(responseText), PChar(responseText), MHD_RESPMEM_MUST_COPY);
  94.     Result := MHD_queue_response(connection, MHD_HTTP_OK, response);
  95.  
  96.     MHD_destroy_response(response);
  97.   end;
  98.  
  99. begin
  100.   // critical section
  101.   StopLock := TCriticalSection.Create;
  102.   try
  103.     Daemon := MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT,
  104.       nil, nil, @HandleRestfulRequest, nil, MHD_OPTION_END);
  105.  
  106.     if Daemon = nil then
  107.     begin
  108.       WriteLn('Error starting HTTP server!');
  109.       Halt(1);
  110.     end;
  111.  
  112.     WriteLn('rest server started at: http://localhost:', PORT);
  113.     WriteLn('Endpoints: /api/status (GET), /api/echo (POST), /shutdown');
  114.  
  115.     // Loop server
  116.     while not ShouldStop do
  117.       Sleep(100);
  118.  
  119.     MHD_stop_daemon(Daemon);
  120.     WriteLn('Server stopped.');
  121.   finally
  122.     StopLock.Free;
  123.   end;
  124. end.
  125.  

what I don't understand is why the following command gives me the expected result
Code: Bash  [Select][+][-]
  1. $ curl http://localhost:8888/api/status
  2. { "status" : "ok", "uptime" : ‘19:31:25}
  3.  

while this command does not want to work
Code: Bash  [Select][+][-]
  1. $ curl -X POST -H "Content-Type: application/json" -d '{"message": "hello from client"}' http://localhost:8888/api/echo
  2. curl: (52) Empty reply from server
  3.  

can anyone figure out where i'm going wrong?

thanks
nomorelogic

zeljko

  • Hero Member
  • *****
  • Posts: 1764
    • http://wiki.lazarus.freepascal.org/User:Zeljan
Re: libmicrohttpd simple example
« Reply #1 on: May 02, 2025, 09:14:07 pm »
Seem that payload isn't uploaded to the client. I've tried your example (also added 2 headers Content-Type and Content-Length and I can see them with -v curl option ), but no luck, payload is missing, headers arrived succesfully.

Thaddy

  • Hero Member
  • *****
  • Posts: 17176
  • Ceterum censeo Trump esse delendam
Re: libmicrohttpd simple example
« Reply #2 on: May 04, 2025, 08:43:45 am »
Out of frustration that I could not find the bug I spend quite a long night to provide my own simple version with the same functionality.
Deepseek and Copilot served as correctors/debuggers.
The threading idea is from deepseek with errror corrected by Copilot.
Code validation done by all three. Quite a lot of code is simply mine.
It supports two threading models:
- per thread
- pooled

Enjoy
Code: Pascal  [Select][+][-]
  1. program microrestserver;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   ctypes, sysutils, classes, baseunix, unixtype, pthreads,
  7.   fpjson, jsonparser, libmicrohttpd;
  8.  
  9. const
  10.   PORT = 8888;// better make that ParamStr(1) or a .conf file
  11.  
  12. var
  13.   server: PMHD_Daemon;
  14.   should_shutdown: boolean = False;
  15.   shutdown_mutex: pthread_mutex_t;
  16.  
  17. type
  18.   TConnectionInfo = record
  19.     post_data: string;
  20.     json_data: TJSONData;  // For JSON payloads
  21.   end;
  22.   PConnectionInfo = ^TConnectionInfo;
  23.  
  24. procedure InitializeMutex;
  25. begin
  26.   if pthread_mutex_init(@shutdown_mutex, nil) <> 0 then
  27.   begin
  28.     WriteLn('Error initializing mutex');
  29.     Halt(1);
  30.   end;
  31. end;
  32.  
  33. function CreateJsonResponse(const Method, Message: string): string;
  34. var
  35.   json: TJSONObject;
  36. begin
  37.   json := TJSONObject.Create;
  38.   try
  39.     json.Add('method', Method);
  40.     json.Add('message', Message);
  41.     Result := json.AsJSON;
  42.   finally
  43.     json.Free;
  44.   end;
  45. end;
  46.  
  47. function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  48.   url: PChar; method: PChar; version: PChar; upload_data: PChar;
  49.   upload_data_size: PCsize_t; ptr: PPointer): cint; cdecl;
  50. var
  51.   response: PMHD_Response;
  52.   ret: cint;
  53.   con_info: PConnectionInfo;
  54.   status: string;
  55.   json_response: string;
  56. begin
  57.   // Initialize or get connection-specific data
  58.   if ptr^ = nil then
  59.   begin
  60.     // First call for this connection
  61.     New(con_info);
  62.     con_info^.post_data := '';
  63.     con_info^.json_data := nil;
  64.     ptr^ := con_info;
  65.     Exit(MHD_YES);
  66.   end;
  67.  
  68.   con_info := PConnectionInfo(ptr^);
  69.  
  70.   // Handle POST data
  71.   if (upload_data_size^ <> 0) and (method = 'POST') then
  72.   begin
  73.     con_info^.post_data := upload_data;
  74.    
  75.     // Try to parse as JSON if content-type is application/json
  76.     if Assigned(MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content-Type')) and
  77.  { don't change this, I discovered libmicrohttpd can return more
  78.    data than just a string with the length you expect.
  79.    Using pos works around that.
  80.    This behavior is documented. }
  81.        (Pos('application/json', MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content-Type')) > 0) then
  82.     begin
  83.       if Assigned(con_info^.json_data) then
  84.         con_info^.json_data.Free;
  85.       try
  86.         con_info^.json_data := GetJSON(con_info^.post_data);
  87.       except
  88.         on E: Exception do
  89.           con_info^.json_data := nil;
  90.       end;
  91.     end;
  92.    
  93.     upload_data_size^ := 0;
  94.     Exit(MHD_YES);
  95.   end;
  96.  
  97.   // Prepare responses based on URL and method
  98.   if (url = '/shutdown') and (method = 'GET') then
  99.   begin
  100.     // Thread-safe shutdown flag update
  101.     pthread_mutex_lock(@shutdown_mutex);
  102.     should_shutdown := True;
  103.     pthread_mutex_unlock(@shutdown_mutex);
  104.  
  105.     response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);
  106.     ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  107.     MHD_destroy_response(response);
  108.    
  109.     // Cleanup connection info
  110.     if Assigned(con_info^.json_data) then
  111.       con_info^.json_data.Free;
  112.     Dispose(con_info);
  113.     ptr^ := nil;
  114.    
  115.     Result := ret;
  116.   end
  117.   else if (url = '/status') and (method = 'GET') then
  118.   begin
  119.     status := CreateJsonResponse('GET', 'Server status: running, time: ' + DateTimeToStr(Now));
  120.     response := MHD_create_response_from_buffer(
  121.       Length(status),
  122.       PChar(status),
  123.       MHD_RESPMEM_MUST_COPY
  124.     );
  125.     MHD_add_response_header(response, 'Content-Type', 'application/json');
  126.     ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  127.     MHD_destroy_response(response);
  128.    
  129.     // Cleanup connection info
  130.     if Assigned(con_info^.json_data) then
  131.       con_info^.json_data.Free;
  132.     Dispose(con_info);
  133.     ptr^ := nil;
  134.    
  135.     Result := ret;
  136.   end
  137.   else if (url = '/api/echo') then
  138.   begin
  139.     if (method = 'GET') then
  140.     begin
  141.       json_response := CreateJsonResponse('GET', 'echo');
  142.     end
  143.     else if (method = 'POST') then
  144.     begin
  145.       if Assigned(con_info^.json_data) then
  146.         json_response := con_info^.json_data.AsJSON
  147.       else
  148.         json_response := CreateJsonResponse('POST', con_info^.post_data);
  149.     end
  150.     else
  151.     begin
  152.       // Unsupported method
  153.       response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);
  154.       ret := MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, response);
  155.       MHD_destroy_response(response);
  156.      
  157.       // Cleanup connection info
  158.       if Assigned(con_info^.json_data) then
  159.         con_info^.json_data.Free;
  160.       Dispose(con_info);
  161.       ptr^ := nil;
  162.      
  163.       Result := ret;
  164.       Exit;
  165.     end;
  166.    
  167.     response := MHD_create_response_from_buffer(
  168.       Length(json_response),
  169.       PChar(json_response),
  170.       MHD_RESPMEM_MUST_COPY
  171.     );
  172.     MHD_add_response_header(response, 'Content-Type', 'application/json');
  173.     ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  174.     MHD_destroy_response(response);
  175.    
  176.     // Cleanup connection info
  177.     if Assigned(con_info^.json_data) then
  178.       con_info^.json_data.Free;
  179.     Dispose(con_info);
  180.     ptr^ := nil;
  181.    
  182.     Result := ret;
  183.   end
  184.   else
  185.   begin
  186.     // Not found
  187.     response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);
  188.     ret := MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
  189.     MHD_destroy_response(response);
  190.    
  191.     // Cleanup connection info
  192.     if Assigned(con_info^.json_data) then
  193.       con_info^.json_data.Free;
  194.     Dispose(con_info);
  195.     ptr^ := nil;
  196.    
  197.     Result := ret;
  198.   end;
  199. end;
  200.  
  201. begin
  202.   // Initialize mutex properly
  203.   InitializeMutex;
  204.  
  205.   // Create the MHD daemon with either thread pool OR thread-per-connection
  206.   // Choose one of these options:
  207.  
  208.   // Option 1: Thread pool (fixed number of threads)
  209.   server := MHD_start_daemon(
  210.     MHD_USE_INTERNAL_POLLING_THREAD or MHD_USE_DEBUG,
  211.     PORT,
  212.     nil,
  213.     nil,
  214.     @HandleRestfulRequest,
  215.     nil,
  216.     MHD_OPTION_THREAD_POOL_SIZE, 4,
  217.     MHD_OPTION_END
  218.   );
  219.  
  220.   {
  221.   // Option 2: Thread-per-connection (one thread per request)
  222.   server := MHD_start_daemon(
  223.     MHD_USE_THREAD_PER_CONNECTION or MHD_USE_DEBUG or MHD_USE_INTERNAL_POLLING_THREAD,
  224.     PORT,
  225.     nil,
  226.     nil,
  227.     @HandleRestfulRequest,
  228.     nil,
  229.     MHD_OPTION_END
  230.   );
  231.   }
  232.  
  233.   if (server = nil) then
  234.   begin
  235.     WriteLn('Error starting server');
  236.     Halt(1);
  237.   end;
  238.  
  239.   WriteLn('Server running on port ', PORT);
  240.   WriteLn('Endpoints:');
  241.   WriteLn('  GET /status - Returns server status');
  242.   WriteLn('  GET /shutdown - Stops the server');
  243.   WriteLn('  GET /api/echo - Simple echo response');
  244.   WriteLn('  POST /api/echo - Echoes back POST data');
  245.  
  246.   // Main loop
  247.   while True do
  248.   begin
  249.     Sleep(100);
  250.     pthread_mutex_lock(@shutdown_mutex);
  251.     if should_shutdown then
  252.     begin
  253.       pthread_mutex_unlock(@shutdown_mutex);
  254.       Break;
  255.     end;
  256.     pthread_mutex_unlock(@shutdown_mutex);
  257.   end;
  258.  
  259.   // Clean up
  260.   MHD_stop_daemon(server);
  261.   pthread_mutex_destroy(@shutdown_mutex);
  262.   WriteLn('Server stopped');
  263. end.
  264.  
Code: Bash  [Select][+][-]
  1. $ ./microrestserver
  2. Server running on port 8888
  3. Endpoints:
  4.   GET /status - Returns server status
  5.   GET /shutdown - Stops the server
  6.   GET /api/echo - Simple echo response
  7.   POST /api/echo - Echoes back POST data
Request:
Code: Bash  [Select][+][-]
  1. $ curl -v -X POST -H "Content-Type: application/json" -d '{"message": "hello"}' http://localhost:8888/api/echo

Response:
Code: Bash  [Select][+][-]
  1. * Host localhost:8888 was resolved.
  2. * IPv6: ::1
  3. * IPv4: 127.0.0.1
  4. *   Trying [::1]:8888...
  5. * connect to ::1 port 8888 from ::1 port 60138 failed: Connection refused
  6. *   Trying 127.0.0.1:8888...
  7. * Connected to localhost (127.0.0.1) port 8888
  8. > POST /api/echo HTTP/1.1
  9. > Host: localhost:8888
  10. > User-Agent: curl/8.5.0
  11. > Accept: */*
  12. > Content-Type: application/json
  13. > Content-Length: 20
  14. >
  15. < HTTP/1.1 200 OK
  16. < Date: Sun, 04 May 2025 06:37:18 GMT
  17. < Content-Type: application/json
  18. < Content-Length: 23
  19. <
  20. * Connection #0 to host localhost left intact
  21. { "message" : "hello" }
BTW: both DeepSeek and CoPilot could not debug your original code....
« Last Edit: May 04, 2025, 12:12:10 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

Fibonacci

  • Hero Member
  • *****
  • Posts: 754
  • Internal Error Hunter
Re: libmicrohttpd simple example
« Reply #3 on: May 04, 2025, 12:42:51 pm »
My quick research and testing showed that since the data can be delivered in multiple parts, only when upload_data_size is 0 can the response finally be sent.

I just save all incoming data in "temp" var, but you should store it per connection in some structure. A pointer to the structure should be stored in con_cls.

Code: Pascal  [Select][+][-]
  1. program restfulhttpserverthreadsafe;
  2.  
  3. {$mode objfpc}{$H+}
  4.  
  5. uses
  6.   cthreads,
  7.   Classes,
  8.   SysUtils,
  9.   cmem,
  10.   libmicrohttpd,
  11.   fpjson,
  12.   jsonparser,
  13.   syncobjs;
  14.  
  15. const
  16.   PORT = 8888;
  17.  
  18. var
  19.   ShouldStop: boolean = False;
  20.   Daemon: PMHD_Daemon;
  21.   StopLock: TCriticalSection;
  22.   temp: string;
  23.  
  24.  
  25.   function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  26.     url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;
  27.     con_cls: PPointer): cint; cdecl;
  28.   var
  29.     responseText: string;
  30.     response: PMHD_Response;
  31.     jsonObj: TJSONObject;
  32.     uploadStr: string;
  33.   begin
  34.     Result := 0;
  35.     if con_cls^ = nil then
  36.     begin
  37.       // first call -> init connection
  38.       con_cls^ := Pointer(1);
  39.       temp := '';
  40.       Exit(MHD_YES);
  41.     end;
  42.  
  43.  
  44.     if StrPas(url) = '/shutdown' then
  45.     begin
  46.       jsonObj := TJSONObject.Create;
  47.       jsonObj.Add('status', 'shutting down');
  48.       responseText := jsonObj.AsJSON;
  49.       jsonObj.Free;
  50.  
  51.       // try gracefully stop
  52.       StopLock.Enter;
  53.       try
  54.         ShouldStop := True;
  55.       finally
  56.         StopLock.Leave;
  57.       end;
  58.     end
  59.     else if (StrPas(url) = '/api/status') and (StrPas(method) = 'GET') then
  60.     begin
  61.       jsonObj := TJSONObject.Create;
  62.       jsonObj.Add('status', 'ok');
  63.       jsonObj.Add('uptime', FormatDateTime('hh:nn:ss', Now));
  64.       responseText := jsonObj.AsJSON;
  65.       jsonObj.Free;
  66.     end
  67.     else if (StrPas(url) = '/api/echo') and (StrPas(method) = 'POST') then
  68.     begin
  69.       if upload_data_size^ <> 0 then begin
  70.         // this is the first or next part of the incoming data, keep it
  71.         SetString(uploadStr, upload_data, upload_data_size^);
  72.         temp += uploadStr;
  73.         upload_data_size^ := 0;
  74.         exit(MHD_YES);
  75.       end else begin
  76.         // no more incoming data, can send reply
  77.       end;
  78.  
  79.       // do we have a playload?
  80.       //if (upload_data <> nil) and (upload_data_size^ > 0) then
  81.       if temp <> '' then
  82.       begin
  83.         try
  84.           // prepare data
  85.           //SetString(uploadStr, upload_data, upload_data_size^);
  86.           uploadStr := temp;
  87.           // "cast" as json
  88.           jsonObj := GetJSON(uploadStr) as TJSONObject;
  89.           responseText := jsonObj.AsJSON;
  90.           jsonObj.Free;
  91.         except
  92.           on E: Exception do
  93.           begin
  94.             // Se c'è un errore nel parsing, restituisco un errore JSON
  95.             responseText := '{"error": "Invalid JSON format"}';
  96.           end;
  97.         end;
  98.  
  99.       end
  100.       else
  101.       begin
  102.         // playload is empty?
  103.         responseText := '{"error": "No data received"}';
  104.       end;
  105.     end;
  106.  
  107.     response := MHD_create_response_from_buffer(Length(responseText), PChar(responseText), MHD_RESPMEM_MUST_COPY);
  108.     Result := MHD_queue_response(connection, MHD_HTTP_OK, response);
  109.  
  110.     MHD_destroy_response(response);
  111.   end;
  112.  
  113. begin
  114.   // critical section
  115.   StopLock := TCriticalSection.Create;
  116.   try
  117.     Daemon := MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT,
  118.       nil, nil, @HandleRestfulRequest, nil, MHD_OPTION_END);
  119.  
  120.     if Daemon = nil then
  121.     begin
  122.       WriteLn('Error starting HTTP server!');
  123.       Halt(1);
  124.     end;
  125.  
  126.     WriteLn('rest server started at: http://localhost:', PORT);
  127.     WriteLn('Endpoints: /api/status (GET), /api/echo (POST), /shutdown');
  128.  
  129.     // Loop server
  130.     while not ShouldStop do
  131.       Sleep(100);
  132.  
  133.     MHD_stop_daemon(Daemon);
  134.     WriteLn('Server stopped.');
  135.   finally
  136.     StopLock.Free;
  137.   end;
  138. end.

Thaddy

  • Hero Member
  • *****
  • Posts: 17176
  • Ceterum censeo Trump esse delendam
Re: libmicrohttpd simple example
« Reply #4 on: May 04, 2025, 12:52:42 pm »
An updated version will some options.
For the curl examples above, specify ./microrestserver 8888
It has all endpoint you required.
You can use the THREADPOOL define to switch between thread pool or thread per connection.
Code: Pascal  [Select][+][-]
  1. program microrestserver;
  2. {$mode objfpc}{$H+}
  3. {$define THREADPOOL}
  4. uses
  5.   ctypes, sysutils, classes, baseunix, unixtype, pthreads,
  6.   fpjson, jsonparser, libmicrohttpd;
  7.  
  8. const
  9.   DEFAULTPORT = 8080;
  10.  
  11. var
  12.   PORT:Cardinal = DEFAULTPORT;
  13.   server: PMHD_Daemon;
  14.   should_shutdown: boolean = False;
  15.   shutdown_mutex: pthread_mutex_t;
  16.  
  17. type
  18.   TConnectionInfo = record
  19.     post_data: string;
  20.     json_data: TJSONData;  // For JSON payloads
  21.   end;
  22.   PConnectionInfo = ^TConnectionInfo;
  23.  
  24. procedure InitializeMutex;
  25. begin
  26.   if pthread_mutex_init(@shutdown_mutex, nil) <> 0 then
  27.   begin
  28.     WriteLn('Error initializing mutex');
  29.     Halt(1);
  30.   end;
  31. end;
  32.  
  33. function CreateJsonResponse(const Method, Message: string): string;
  34. var
  35.   json: TJSONObject;
  36. begin
  37.   json := TJSONObject.Create;
  38.   try
  39.     json.Add('method', Method);
  40.     json.Add('message', Message);
  41.     Result := json.AsJSON;
  42.   finally
  43.     json.Free;
  44.   end;
  45. end;
  46.  
  47. function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  48.   url: PChar; method: PChar; version: PChar; upload_data: PChar;
  49.   upload_data_size: PCsize_t; ptr: PPointer): cint; cdecl;
  50. var
  51.   response: PMHD_Response;
  52.   ret: cint;
  53.   con_info: PConnectionInfo;
  54.   status: string;
  55.   json_response: string;
  56. begin
  57.   // Initialize or get connection-specific data
  58.   if ptr^ = nil then
  59.   begin
  60.     // First call for this connection
  61.     New(con_info);
  62.     con_info^.post_data := '';
  63.     con_info^.json_data := nil;
  64.     ptr^ := con_info;
  65.     Exit(MHD_YES);
  66.   end;
  67.  
  68.   con_info := PConnectionInfo(ptr^);
  69.  
  70.   // Handle POST data
  71.   if (upload_data_size^ <> 0) and (method = 'POST') then
  72.   begin
  73.     con_info^.post_data := upload_data;
  74.    
  75.     // Try to parse as JSON if content-type is application/json
  76.     if Assigned(MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content-Type')) and
  77.        (Pos('application/json', MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content-Type')) > 0) then
  78.     begin
  79.       if Assigned(con_info^.json_data) then
  80.         con_info^.json_data.Free;
  81.       try
  82.         con_info^.json_data := GetJSON(con_info^.post_data);
  83.       except
  84.         on E: Exception do
  85.           con_info^.json_data := nil;
  86.       end;
  87.     end;
  88.    
  89.     upload_data_size^ := 0;
  90.     Exit(MHD_YES);
  91.   end;
  92.  
  93.   // Prepare responses based on URL and method
  94.   if (url = '/shutdown') and (method = 'GET') then
  95.   begin
  96.     // Thread-safe shutdown flag update
  97.     pthread_mutex_lock(@shutdown_mutex);
  98.     should_shutdown := True;
  99.     pthread_mutex_unlock(@shutdown_mutex);
  100.  
  101.     response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);
  102.     ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  103.     MHD_destroy_response(response);
  104.    
  105.     // Cleanup connection info
  106.     if Assigned(con_info^.json_data) then
  107.       con_info^.json_data.Free;
  108.     Dispose(con_info);
  109.     ptr^ := nil;
  110.    
  111.     Result := ret;
  112.   end
  113.   else if (url = '/status') and (method = 'GET') then
  114.   begin
  115.     status := CreateJsonResponse('GET', 'Server status: running, time: ' + DateTimeToStr(Now));
  116.     response := MHD_create_response_from_buffer(
  117.       Length(status),
  118.       PChar(status),
  119.       MHD_RESPMEM_MUST_COPY
  120.     );
  121.     MHD_add_response_header(response, 'Content-Type', 'application/json');
  122.     ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  123.     MHD_destroy_response(response);
  124.    
  125.     // Cleanup connection info
  126.     if Assigned(con_info^.json_data) then
  127.       con_info^.json_data.Free;
  128.     Dispose(con_info);
  129.     ptr^ := nil;
  130.    
  131.     Result := ret;
  132.   end
  133.   else if (url = '/api/echo') then
  134.   begin
  135.     if (method = 'GET') then
  136.     begin
  137.       json_response := CreateJsonResponse('GET', 'echo');
  138.     end
  139.     else if (method = 'POST') then
  140.     begin
  141.       if Assigned(con_info^.json_data) then
  142.         json_response := con_info^.json_data.AsJSON
  143.       else
  144.         json_response := CreateJsonResponse('POST', con_info^.post_data);
  145.     end
  146.     else
  147.     begin
  148.       // Unsupported method
  149.       response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);
  150.       ret := MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, response);
  151.       MHD_destroy_response(response);
  152.      
  153.       // Cleanup connection info
  154.       if Assigned(con_info^.json_data) then
  155.         con_info^.json_data.Free;
  156.       Dispose(con_info);
  157.       ptr^ := nil;
  158.      
  159.       Result := ret;
  160.       Exit;
  161.     end;
  162.    
  163.     response := MHD_create_response_from_buffer(
  164.       Length(json_response),
  165.       PChar(json_response),
  166.       MHD_RESPMEM_MUST_COPY
  167.     );
  168.     MHD_add_response_header(response, 'Content-Type', 'application/json');
  169.     ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  170.     MHD_destroy_response(response);
  171.    
  172.     // Cleanup connection info
  173.     if Assigned(con_info^.json_data) then
  174.       con_info^.json_data.Free;
  175.     Dispose(con_info);
  176.     ptr^ := nil;
  177.    
  178.     Result := ret;
  179.   end
  180.   else
  181.   begin
  182.     // Not found
  183.     response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);
  184.     ret := MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);
  185.     MHD_destroy_response(response);
  186.    
  187.     // Cleanup connection info
  188.     if Assigned(con_info^.json_data) then
  189.       con_info^.json_data.Free;
  190.     Dispose(con_info);
  191.     ptr^ := nil;
  192.    
  193.     Result := ret;
  194.   end;
  195. end;
  196.  
  197. begin
  198.   // Initialize mutex properly
  199.   InitializeMutex;
  200.   If ParamCount > 0 then
  201.   begin
  202.     // try to read a port at Paramstr(1),
  203.     // if it fails, use the default port
  204.     PORT := StrToIntDef(ParamStr(1), DEFAULTPORT);
  205.   end
  206.   else
  207.     PORT :=DEFAULTPORT;
  208.  
  209.   // Create the MHD daemon with either thread pool OR thread-per-connection
  210.   // Choose one of these options:
  211.  
  212.   // Option 1: Thread pool (fixed number of threads)
  213.   {$if Defined(THREADPOOL)}
  214.   server := MHD_start_daemon(
  215.     MHD_USE_INTERNAL_POLLING_THREAD or MHD_USE_DEBUG,
  216.     PORT,
  217.     nil,
  218.     nil,
  219.     @HandleRestfulRequest,
  220.     nil,
  221.     MHD_OPTION_THREAD_POOL_SIZE, 4,
  222.     MHD_OPTION_END
  223.   );
  224.  
  225.   {$else thread_per_connection}
  226.   // Option 2: Thread-per-connection (one thread per request)
  227.   server := MHD_start_daemon(
  228.     MHD_USE_THREAD_PER_CONNECTION or MHD_USE_DEBUG or MHD_USE_INTERNAL_POLLING_THREAD,
  229.     PORT,
  230.     nil,
  231.     nil,
  232.     @HandleRestfulRequest,
  233.     nil,
  234.     MHD_OPTION_END
  235.   );
  236.   {$ifend}
  237.  
  238.   if (server = nil) then
  239.   begin
  240.     WriteLn('Error starting server');
  241.     Halt(1);
  242.   end;
  243.   Writeln('-----------------');
  244.   Writeln('Micro Rest Server');
  245.   Writeln('-----------------');  
  246.   Writeln('You can specify a port number on the command line.');
  247.   Writeln('If it not a valid port, the default port 8080 will');
  248.   Writeln('be used.');
  249.   Writeln;
  250.   WriteLn('Server running on port ', PORT);
  251.   WriteLn('Endpoints:');
  252.   WriteLn('  GET /status - Returns server status');
  253.   WriteLn('  GET /shutdown - Stops the server');
  254.   WriteLn('  GET /api/echo - Simple echo response');
  255.   WriteLn('  POST /api/echo - Echoes back POST data');
  256.  
  257.   // Main loop
  258.   while True do
  259.   begin
  260.     Sleep(100);
  261.     pthread_mutex_lock(@shutdown_mutex);
  262.     if should_shutdown then
  263.     begin
  264.       pthread_mutex_unlock(@shutdown_mutex);
  265.       Break;
  266.     end;
  267.     pthread_mutex_unlock(@shutdown_mutex);
  268.   end;
  269.   writeln;
  270.   // Clean up
  271.   MHD_stop_daemon(server);
  272.   pthread_mutex_destroy(@shutdown_mutex);
  273.   WriteLn('Server stopped');
  274. end.

« Last Edit: May 06, 2025, 06:31:32 am by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

Thaddy

  • Hero Member
  • *****
  • Posts: 17176
  • Ceterum censeo Trump esse delendam
Re: libmicrohttpd simple example
« Reply #5 on: May 04, 2025, 12:59:06 pm »
My quick research and testing showed that since the data can be delivered in multiple parts, only when upload_data_size is 0 can the response finally be sent.

Are you sure you got it to work?
That's what I tried too last evening and did not succeed, but there are more things wrong, e.g. the server crashes on shutdown and the threading model is dubious.
Hence my approach which is a bit more structured and has a much better threading model.
It can be used as a template for more serious servers since all ingredients are included.
I think it has the simple approach that he asked for.
« Last Edit: May 04, 2025, 01:10:20 pm by Thaddy »
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

Fibonacci

  • Hero Member
  • *****
  • Posts: 754
  • Internal Error Hunter
Re: libmicrohttpd simple example
« Reply #6 on: May 04, 2025, 01:04:25 pm »
Are you sure you got it to work?

Seems to work

the server crashes on shutdown

It does not

Thaddy

  • Hero Member
  • *****
  • Posts: 17176
  • Ceterum censeo Trump esse delendam
Re: libmicrohttpd simple example
« Reply #7 on: May 04, 2025, 01:18:33 pm »
Well, not going to argue.
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

Fibonacci

  • Hero Member
  • *****
  • Posts: 754
  • Internal Error Hunter
Re: libmicrohttpd simple example
« Reply #8 on: May 04, 2025, 01:24:23 pm »
Well, not going to argue.

You sure? You usually do >:D

I just found the problem and fixed OPs code.

Your code is more sophisticated and is probably better, good job!

PS. I see it. At first I thought you meant it crashes always, not like "it can" (and will some day).
« Last Edit: May 04, 2025, 01:51:56 pm by Fibonacci »

Thaddy

  • Hero Member
  • *****
  • Posts: 17176
  • Ceterum censeo Trump esse delendam
Re: libmicrohttpd simple example
« Reply #9 on: May 04, 2025, 01:52:28 pm »
You did better than both of the two prominent AI engines... Took hours and we were going nowhere. They even did disagree with eachother!  %) :D
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

nomorelogic

  • Full Member
  • ***
  • Posts: 192
Re: libmicrohttpd simple example
« Reply #10 on: May 05, 2025, 09:27:16 am »
thank you all for your replies
I was really busy last days
as soon as possible I'll test all your code

thanks again

nomorelogic

  • Full Member
  • ***
  • Posts: 192
Re: libmicrohttpd simple example
« Reply #11 on: May 06, 2025, 10:03:54 am »
[...]
Hence my approach which is a bit more structured and has a much better threading model.
It can be used as a template for more serious servers since all ingredients are included.
I think it has the simple approach that he asked for.

thanks Thaddy for your help
in fact what I wanted to achieve was a project as simple as possible (and complete) to use as a template for more serious implementations.

By reading more about libmicrohttpd and studying both your code and Fibonacci's code, I think I have worked out where the bug might lie. I will look into it further and post any improvements here.

In the meantime, I wanted to tell you that your code compiles correctly on my system (Devuan Linux + Lazarus Trunk). However, I get the following error when I invoke the api "status."
What environment are you developing on?

Code: Bash  [Select][+][-]
  1. $ ./microrestserver &
  2. [1] 27389
  3. Server running on port 8888
  4. Endpoints:
  5.   GET /status - Returns server status
  6.   GET /shutdown - Stops the server
  7.   GET /api/echo - Simple echo response
  8.   POST /api/echo - Echoes back POST data
  9.  
  10. $ curl http://localhost:8888/api/status
  11. An unhandled exception occurred at $00000000004197CF:
  12. EStackOverflow: Stack overflow or stack misalignment
  13.   $00000000004197CF
  14.   $00007F3490FE91AC
  15.   $FFFE7BFCFFFE7B94
  16.  
  17. Heap dump by heaptrc unit of "./microrestserver"
  18. 330 memory blocks allocated : 38636/38720
  19. 327 memory blocks freed     : 38436/38520
  20. 3 unfreed memory blocks : 200
  21. True heap size : 196608
  22. True free heap : 195744
  23. Should be : 195832
  24. Call trace for block $00007F348EF22100 size 128
  25.   $00000000004158B0
  26.   $00000000004197CF
  27.   $00007F3490FE91AC
  28. Call trace for block $00007F349102C600 size 40
  29.   $00000000004158B0
  30.   $00000000004197CF
  31.   $00007F3490FE91AC
  32. Call trace for block $00007F349102C500 size 32
  33.   $00000000004197CF
  34.   $00007F3490FE91AC
  35. curl: (52) Empty reply from server
  36. [1]+  Uscita 217              ./microrestserver
  37.  


Edit:
version of libmicrohttpd in my system:
libmicrohttpd-dev/stable,now 0.9.75-6 amd64 [installed]
« Last Edit: May 06, 2025, 10:14:50 am by nomorelogic »

nomorelogic

  • Full Member
  • ***
  • Posts: 192
Re: libmicrohttpd simple example
« Reply #12 on: May 06, 2025, 10:09:15 am »
My quick research and testing showed that since the data can be delivered in multiple parts, only when upload_data_size is 0 can the response finally be sent.
I just save all incoming data in "temp" var, but you should store it per connection in some structure. A pointer to the structure should be stored in con_cls.

thanks Fibonacci
your fix with the "temp" variable seems to work, now I have to use a dedicated data structure for the context.
As already explained in the previous post, thanks to your posts, I think I understand where the problem is.
I will keep you updated.

Thaddy

  • Hero Member
  • *****
  • Posts: 17176
  • Ceterum censeo Trump esse delendam
Re: libmicrohttpd simple example
« Reply #13 on: May 06, 2025, 10:40:39 am »
I will look into it, but my second version is running happily over here.
What is your exact platform?
Due to censorship, I changed this to "Nelly the Elephant". Keeps the message clear.

nomorelogic

  • Full Member
  • ***
  • Posts: 192
Re: libmicrohttpd simple example
« Reply #14 on: May 06, 2025, 11:44:42 am »
my platform is in attachment

Just to know, I also noticed a little difference in HandleRestfulRequest declarations.
I declared upload_data_size using libmicrohttpd.Psize_t while you're using a type in aliastcp.inc
I have not yet realised whether it could be the problem, but I wanted to point it out as it may be useful.

Your declaration:
Code: Pascal  [Select][+][-]
  1. function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  2.   url: PChar; method: PChar; version: PChar; upload_data: PChar;
  3.   upload_data_size: PCsize_t; ptr: PPointer): cint; cdecl;
  4.  


Mine declaration:
Code: Pascal  [Select][+][-]
  1.   function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  2.     url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;
  3.     con_cls: PPointer): cint; cdecl;
  4.  

 

TinyPortal © 2005-2018