libmicrohttpd simple example lazarus / 38-Networking and Web Programming ---------------------------------------------------------------------- nomorelogic · 2025-05-02 17:39 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program restfulhttpserverthreadsafe; {$mode objfpc}{$H+} uses  cthreads,  Classes,  SysUtils,  cmem,  libmicrohttpd,  fpjson,  jsonparser,  syncobjs; const  PORT = 8888; var  ShouldStop: boolean = False;  Daemon: PMHD_Daemon;  StopLock: TCriticalSection;    function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;    url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;    con_cls: PPointer): cint; cdecl;  var    responseText: string;    response: PMHD_Response;    jsonObj: TJSONObject;    uploadStr: string;  begin    Result := 0;    if con_cls^ = nil then  begin      // first call -> init connection      con_cls^ := Pointer(1);      Exit(MHD_YES);    end;      if StrPas(url) = '/shutdown' then    begin      jsonObj := TJSONObject.Create;      jsonObj.Add('status', 'shutting down');      responseText := jsonObj.AsJSON;      jsonObj.Free;       // try gracefully stop      StopLock.Enter;      try        ShouldStop := True;      finally      StopLock.Leave;      end;    end    else if (StrPas(url) = '/api/status') and (StrPas(method) = 'GET') then    begin      jsonObj := TJSONObject.Create;      jsonObj.Add('status', 'ok');      jsonObj.Add('uptime', FormatDateTime('hh:nn:ss', Now));      responseText := jsonObj.AsJSON;      jsonObj.Free;    end    else if (StrPas(url) = '/api/echo') and (StrPas(method) = 'POST') then    begin      // do we have a playload?      if (upload_data <> nil) and (upload_data_size^ > 0) then      begin        try          // prepare data          SetString(uploadStr, upload_data, upload_data_size^);        // "cast" as json          jsonObj := GetJSON(uploadStr) as TJSONObject;          responseText := jsonObj.AsJSON;          jsonObj.Free;        except          on E: Exception do          begin            // Se c'è un errore nel parsing, restituisco un errore JSON            responseText := '{"error": "Invalid JSON format"}';          end;        end;       end      else      begin      // playload is empty?        responseText := '{"error": "No data received"}';      end;    end;     response := MHD_create_response_from_buffer(Length(responseText), PChar(responseText), MHD_RESPMEM_MUST_COPY);    Result := MHD_queue_response(connection, MHD_HTTP_OK, response);     MHD_destroy_response(response);  end; begin  // critical section  StopLock := TCriticalSection.Create;  try    Daemon := MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT,      nil, nil, @HandleRestfulRequest, nil, MHD_OPTION_END);     if Daemon = nil then    begin      WriteLn('Error starting HTTP server!');      Halt(1);    end;     WriteLn('rest server started at: http://localhost:', PORT);  WriteLn('Endpoints: /api/status (GET), /api/echo (POST), /shutdown');     // Loop server    while not ShouldStop do      Sleep(100);     MHD_stop_daemon(Daemon);    WriteLn('Server stopped.');  finally    StopLock.Free;  end;end.  what I don't understand is why the following command gives me the expected result Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ curl http://localhost:8888/api/status{ "status" : "ok", "uptime" : ‘19:31:25’ }  while this command does not want to work Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ curl -X POST -H "Content-Type: application/json" -d '{"message": "hello from client"}' http://localhost:8888/api/echocurl: (52) Empty reply from server  can anyone figure out where i'm going wrong? thanks nomorelogic ---------------------------------------------------------------------- zeljko · 2025-05-02 19:14 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 · 2025-05-04 06:43 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program microrestserver; {$mode objfpc}{$H+} uses  ctypes, sysutils, classes, baseunix, unixtype, pthreads,   fpjson, jsonparser, libmicrohttpd; const  PORT = 8888;// better make that ParamStr(1) or a .conf file var  server: PMHD_Daemon;  should_shutdown: boolean = False;  shutdown_mutex: pthread_mutex_t; type  TConnectionInfo = record    post_data: string;    json_data: TJSONData;  // For JSON payloads  end;  PConnectionInfo = ^TConnectionInfo; procedure InitializeMutex;begin  if pthread_mutex_init(@shutdown_mutex, nil) <> 0 then  begin    WriteLn('Error initializing mutex');    Halt(1);  end;end; function CreateJsonResponse(const Method, Message: string): string;var  json: TJSONObject;begin  json := TJSONObject.Create;  try    json.Add('method', Method);    json.Add('message', Message);    Result := json.AsJSON;  finally    json.Free;  end;end; function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;   url: PChar; method: PChar; version: PChar; upload_data: PChar;  upload_data_size: PCsize_t; ptr: PPointer): cint; cdecl;var  response: PMHD_Response;  ret: cint;  con_info: PConnectionInfo;  status: string;  json_response: string;begin  // Initialize or get connection- specific data  if ptr^ = nil then  begin    // First call for this connection    New(con_info);    con_info^.post_data := '';    con_info^.json_data := nil;    ptr^ := con_info;    Exit(MHD_YES);  end;   con_info := PConnectionInfo(ptr^);   // Handle POST data  if (upload_data_size^ <> 0) and (method = 'POST') then  begin    con_info^.post_data := upload_data;        // Try to parse as JSON if content-type is application/json    if Assigned(MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content-Type')) and { don't change this, I discovered libmicrohttpd can return more   data than just a string with the length you expect.  Using pos works around that.   This behavior is documented. }       (Pos('application/json', MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content-Type')) > 0) then    begin      if Assigned(con_info^.json_data) then        con_info^.json_data.Free;    try        con_info^.json_data := GetJSON(con_info^.post_data);    except        on E: Exception do          con_info^.json_data := nil;      end;    end;        upload_data_size^ := 0;    Exit(MHD_YES);  end;   // Prepare responses based on URL and method  if (url = '/shutdown') and (method = 'GET') then  begin    // Thread-safe shutdown flag update    pthread_mutex_lock(@shutdown_mutex);    should_shutdown := True;    pthread_mutex_unlock(@shutdown_mutex);   response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);    ret := MHD_queue_response(connection, MHD_HTTP_OK, response);    MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;    Dispose(con_info);    ptr^ := nil;      Result := ret;  end  else if (url = '/status') and (method = 'GET') then  begin    status := CreateJsonResponse('GET', 'Server status: running, time: ' + DateTimeToStr(Now));    response := MHD_create_response_from_buffer(      Length(status),      PChar(status),      MHD_RESPMEM_MUST_COPY    );    MHD_add_response_header(response, 'Content-Type', 'application/json');    ret := MHD_queue_response(connection, MHD_HTTP_OK, response);    MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;    Dispose(con_info);    ptr^ := nil;      Result := ret;  end  else if (url = '/api/echo') then  begin    if (method = 'GET') then    begin      json_response := CreateJsonResponse('GET', 'echo');    end    else if (method = 'POST') then    begin      if Assigned(con_info^.json_data) then        json_response := con_info^.json_data.AsJSON      else        json_response := CreateJsonResponse('POST', con_info^.post_data);    end    else    begin      // Unsupported method      response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);      ret := MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, response);      MHD_destroy_response(response);            // Cleanup connection info      if Assigned(con_info^.json_data) then        con_info^.json_data.Free;      Dispose(con_info);      ptr^ := nil;          Result := ret;      Exit;    end;        response := MHD_create_response_from_buffer(      Length(json_response),      PChar(json_response),      MHD_RESPMEM_MUST_COPY    );    MHD_add_response_header(response, 'Content-Type', 'application/json');    ret := MHD_queue_response(connection, MHD_HTTP_OK, response);    MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;    Dispose(con_info);    ptr^ := nil;      Result := ret;  end  else  begin    // Not found    response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);    ret := MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);  MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;  Dispose(con_info);    ptr^ := nil;        Result := ret;  end;end; begin  // Initialize mutex properly  InitializeMutex;   // Create the MHD daemon with either thread pool OR thread-per- connection  // Choose one of these options:    // Option 1: Thread pool (fixed number of threads)  server := MHD_start_daemon(    MHD_USE_INTERNAL_POLLING_THREAD or MHD_USE_DEBUG,    PORT,    nil,    nil,    @HandleRestfulRequest,    nil,    MHD_OPTION_THREAD_POOL_SIZE, 4,    MHD_OPTION_END  );    {  // Option 2: Thread-per-connection (one thread per request)  server := MHD_start_daemon(    MHD_USE_THREAD_PER_CONNECTION or MHD_USE_DEBUG or MHD_USE_INTERNAL_POLLING_THREAD,    PORT,    nil,    nil,    @HandleRestfulRequest,    nil,    MHD_OPTION_END  );  }    if (server = nil) then  begin    WriteLn('Error starting server');    Halt(1);  end;    WriteLn('Server running on port ', PORT);  WriteLn('Endpoints:');  WriteLn('  GET /status - Returns server status');  WriteLn('  GET /shutdown - Stops the server');  WriteLn('  GET /api/echo - Simple echo response');  WriteLn('  POST /api/echo - Echoes back POST data');    // Main loop  while True do  begin    Sleep(100);    pthread_mutex_lock(@shutdown_mutex);    if should_shutdown then    begin      pthread_mutex_unlock(@shutdown_mutex);      Break;    end;    pthread_mutex_unlock(@shutdown_mutex);  end;    // Clean up  MHD_stop_daemon(server);  pthread_mutex_destroy(@shutdown_mutex);  WriteLn('Server stopped');end. Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ ./microrestserverServer running on port 8888Endpoints:  GET /status - Returns server status  GET /shutdown - Stops the server  GET /api/echo - Simple echo response  POST /api/echo - Echoes back POST dataRequest: Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ curl -v -X POST -H "Content-Type: application/json" -d '{"message": "hello"}' http://localhost:8888/api/echo Response: Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i * Host localhost:8888 was resolved.* IPv6: ::1* IPv4: 127.0.0.1*   Trying [::1]:8888...* connect to ::1 port 8888 from ::1 port 60138 failed: Connection refused*   Trying 127.0.0.1:8888...* Connected to localhost (127.0.0.1) port 8888> POST /api/echo HTTP/1.1> Host: localhost:8888> User-Agent: curl/8.5.0> Accept: */*> Content-Type: application/json> Content-Length: 20>< HTTP/1.1 200 OK< Date: Sun, 04 May 2025 06:37:18 GMT< Content-Type: application/json< Content-Length: 23<* Connection #0 to host localhost left intact{ "message" : "hello" }BTW: both DeepSeek and CoPilot could not debug your original code.... ---------------------------------------------------------------------- Fibonacci · 2025-05-04 10:42 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program restfulhttpserverthreadsafe; {$mode objfpc}{$H+} uses  cthreads,  Classes,  SysUtils,  cmem,  libmicrohttpd,  fpjson,  jsonparser,  syncobjs; const  PORT = 8888; var  ShouldStop: boolean = False;  Daemon: PMHD_Daemon;  StopLock: TCriticalSection;  temp: string;    function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;    url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;    con_cls: PPointer): cint; cdecl;  var  responseText: string;    response: PMHD_Response;    jsonObj: TJSONObject;    uploadStr: string;  begin    Result := 0;    if con_cls^ = nil then    begin      // first call -> init connection    con_cls^ := Pointer(1);      temp := '';      Exit(MHD_YES);    end;      if StrPas(url) = '/shutdown' then    begin      jsonObj := TJSONObject.Create;      jsonObj.Add('status', 'shutting down');      responseText := jsonObj.AsJSON;      jsonObj.Free;       // try gracefully stop      StopLock.Enter;      try        ShouldStop := True;      finally        StopLock.Leave;      end;    end    else if (StrPas(url) = '/api/status') and (StrPas(method) = 'GET') then    begin      jsonObj := TJSONObject.Create;      jsonObj.Add('status', 'ok');      jsonObj.Add('uptime', FormatDateTime('hh:nn:ss', Now));    responseText := jsonObj.AsJSON;      jsonObj.Free;    end    else if (StrPas(url) = '/api/echo') and (StrPas(method) = 'POST') then    begin      if upload_data_size^ <> 0 then begin        // this is the first or next part of the incoming data, keep it        SetString(uploadStr, upload_data, upload_data_size^);        temp += uploadStr;        upload_data_size^ := 0;        exit(MHD_YES);      end else begin        // no more incoming data, can send reply      end;       // do we have a playload?      //if (upload_data <> nil) and (upload_data_size^ > 0) then      if temp <> '' then      begin      try          // prepare data          //SetString(uploadStr, upload_data, upload_data_size^);          uploadStr := temp;          // "cast" as json          jsonObj := GetJSON(uploadStr) as TJSONObject;          responseText := jsonObj.AsJSON;          jsonObj.Free;        except          on E: Exception do          begin            // Se c'è un errore nel parsing, restituisco un errore JSON            responseText := '{"error": "Invalid JSON format"}';          end;        end;       end      else      begin      // playload is empty?        responseText := '{"error": "No data received"}';      end;    end;     response := MHD_create_response_from_buffer(Length(responseText), PChar(responseText), MHD_RESPMEM_MUST_COPY);    Result := MHD_queue_response(connection, MHD_HTTP_OK, response);     MHD_destroy_response(response);  end; begin  // critical section  StopLock := TCriticalSection.Create;  try    Daemon := MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, PORT,      nil, nil, @HandleRestfulRequest, nil, MHD_OPTION_END);     if Daemon = nil then    begin      WriteLn('Error starting HTTP server!');      Halt(1);    end;     WriteLn('rest server started at: http://localhost:', PORT);  WriteLn('Endpoints: /api/status (GET), /api/echo (POST), /shutdown');     // Loop server    while not ShouldStop do      Sleep(100);     MHD_stop_daemon(Daemon);    WriteLn('Server stopped.');  finally    StopLock.Free;  end;end. ---------------------------------------------------------------------- Thaddy · 2025-05-04 10:52 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program microrestserver;{$mode objfpc}{$H+}{$define THREADPOOL}uses  ctypes, sysutils, classes, baseunix, unixtype, pthreads,   fpjson, jsonparser, libmicrohttpd; const  DEFAULTPORT = 8080; var  PORT:Cardinal = DEFAULTPORT;  server: PMHD_Daemon;  should_shutdown: boolean = False;  shutdown_mutex: pthread_mutex_t; type  TConnectionInfo = record    post_data: string;    json_data: TJSONData;  // For JSON payloads  end;  PConnectionInfo = ^TConnectionInfo; procedure InitializeMutex;begin  if pthread_mutex_init(@shutdown_mutex, nil) <> 0 then  begin    WriteLn('Error initializing mutex');    Halt(1);  end;end; function CreateJsonResponse(const Method, Message: string): string;var  json: TJSONObject;begin  json := TJSONObject.Create;  try    json.Add('method', Method);    json.Add('message', Message);    Result := json.AsJSON;  finally    json.Free;  end;end; function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;   url: PChar; method: PChar; version: PChar; upload_data: PChar;  upload_data_size: PCsize_t; ptr: PPointer): cint; cdecl;var  response: PMHD_Response;  ret: cint;  con_info: PConnectionInfo;  status: string;  json_response: string;begin  // Initialize or get connection- specific data  if ptr^ = nil then  begin    // First call for this connection    New(con_info);    con_info^.post_data := '';    con_info^.json_data := nil;    ptr^ := con_info;    Exit(MHD_YES);  end;   con_info := PConnectionInfo(ptr^);   // Handle POST data  if (upload_data_size^ <> 0) and (method = 'POST') then  begin    con_info^.post_data := upload_data;        // Try to parse as JSON if content-type is application/json    if Assigned(MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content-Type')) and       (Pos('application/json', MHD_lookup_connection_value(connection, MHD_HEADER_KIND, 'Content- Type')) > 0) then    begin      if Assigned(con_info^.json_data) then        con_info^.json_data.Free;      try        con_info^.json_data := GetJSON(con_info^.post_data);      except        on E: Exception do        con_info^.json_data := nil;      end;    end;        upload_data_size^ := 0;    Exit(MHD_YES);  end;   // Prepare responses based on URL and method  if (url = '/shutdown') and (method = 'GET') then  begin    // Thread-safe shutdown flag update    pthread_mutex_lock(@shutdown_mutex);    should_shutdown := True;    pthread_mutex_unlock(@shutdown_mutex);     response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);    ret := MHD_queue_response(connection, MHD_HTTP_OK, response);    MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;  Dispose(con_info);    ptr^ := nil;        Result := ret;  end  else if (url = '/status') and (method = 'GET') then  begin    status := CreateJsonResponse('GET', 'Server status: running, time: ' + DateTimeToStr(Now));    response := MHD_create_response_from_buffer(    Length(status),      PChar(status),      MHD_RESPMEM_MUST_COPY    );    MHD_add_response_header(response, 'Content-Type', 'application/json');    ret := MHD_queue_response(connection, MHD_HTTP_OK, response);    MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;    Dispose(con_info);    ptr^ := nil;      Result := ret;  end  else if (url = '/api/echo') then  begin    if (method = 'GET') then    begin      json_response := CreateJsonResponse('GET', 'echo');    end    else if (method = 'POST') then    begin      if Assigned(con_info^.json_data) then        json_response := con_info^.json_data.AsJSON      else        json_response := CreateJsonResponse('POST', con_info^.post_data);    end    else    begin      // Unsupported method      response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);      ret := MHD_queue_response(connection, MHD_HTTP_METHOD_NOT_ALLOWED, response);      MHD_destroy_response(response);            // Cleanup connection info      if Assigned(con_info^.json_data) then        con_info^.json_data.Free;      Dispose(con_info);      ptr^ := nil;          Result := ret;      Exit;    end;        response := MHD_create_response_from_buffer(      Length(json_response),      PChar(json_response),      MHD_RESPMEM_MUST_COPY    );    MHD_add_response_header(response, 'Content-Type', 'application/json');    ret := MHD_queue_response(connection, MHD_HTTP_OK, response);    MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;    Dispose(con_info);    ptr^ := nil;      Result := ret;  end  else  begin    // Not found    response := MHD_create_response_from_buffer(0, nil, MHD_RESPMEM_PERSISTENT);    ret := MHD_queue_response(connection, MHD_HTTP_NOT_FOUND, response);  MHD_destroy_response(response);        // Cleanup connection info    if Assigned(con_info^.json_data) then      con_info^.json_data.Free;  Dispose(con_info);    ptr^ := nil;        Result := ret;  end;end; begin  // Initialize mutex properly  InitializeMutex;  If ParamCount > 0 then  begin    // try to read a port at Paramstr(1),   // if it fails, use the default port    PORT := StrToIntDef(ParamStr(1), DEFAULTPORT);  end   else    PORT :=DEFAULTPORT;   // Create the MHD daemon with either thread pool OR thread-per-connection  // Choose one of these options:    // Option 1: Thread pool (fixed number of threads)  {$if Defined(THREADPOOL)}  server := MHD_start_daemon(    MHD_USE_INTERNAL_POLLING_THREAD or MHD_USE_DEBUG,    PORT,    nil,    nil,    @HandleRestfulRequest,    nil,    MHD_OPTION_THREAD_POOL_SIZE, 4,    MHD_OPTION_END  );    {$else thread_per_connection}  // Option 2: Thread-per-connection (one thread per request)  server := MHD_start_daemon(    MHD_USE_THREAD_PER_CONNECTION or MHD_USE_DEBUG or MHD_USE_INTERNAL_POLLING_THREAD,    PORT,    nil,    nil,    @HandleRestfulRequest,    nil,    MHD_OPTION_END  );  {$ifend}    if (server = nil) then  begin    WriteLn('Error starting server');    Halt(1);  end;  Writeln('-----------------');  Writeln('Micro Rest Server');  Writeln('-----------------');    Writeln('You can specify a port number on the command line.');  Writeln('If it not a valid port, the default port 8080 will');  Writeln('be used.');  Writeln;  WriteLn('Server running on port ', PORT);  WriteLn('Endpoints:');  WriteLn('  GET /status - Returns server status');  WriteLn('  GET /shutdown - Stops the server');  WriteLn('  GET /api/echo - Simple echo response');  WriteLn('  POST /api/echo - Echoes back POST data');    // Main loop  while True do  begin    Sleep(100);    pthread_mutex_lock(@shutdown_mutex);    if should_shutdown then    begin      pthread_mutex_unlock(@shutdown_mutex);      Break;    end;    pthread_mutex_unlock(@shutdown_mutex);  end;  writeln;  // Clean up  MHD_stop_daemon(server);  pthread_mutex_destroy(@shutdown_mutex);  WriteLn('Server stopped');end. ---------------------------------------------------------------------- Thaddy · 2025-05-04 10:59 Quote from: Fibonacci on May 04, 2025, 12:42:51 pmMy 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. ---------------------------------------------------------------------- Fibonacci · 2025-05-04 11:04 Quote from: Thaddy on May 04, 2025, 12:59:06 pmAre you sure you got it to work? Seems to work Quote from: Thaddy on May 04, 2025, 12:59:06 pmthe server crashes on shutdown It does not ---------------------------------------------------------------------- Thaddy · 2025-05-04 11:18 Well, not going to argue. ---------------------------------------------------------------------- Fibonacci · 2025-05-04 11:24 Quote from: Thaddy on May 04, 2025, 01:18:33 pmWell, not going to argue. You sure? You usually do 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). ---------------------------------------------------------------------- Thaddy · 2025-05-04 11:52 You did better than both of the two prominent AI engines... Took hours and we were going nowhere. They even did disagree with eachother! ---------------------------------------------------------------------- nomorelogic · 2025-05-05 07:27 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 · 2025-05-06 08:03 Quote from: Thaddy on May 04, 2025, 12:59:06 pm[...] 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i $ ./microrestserver &[1] 27389Server running on port 8888Endpoints:  GET /status - Returns server status  GET /shutdown - Stops the server  GET /api/echo - Simple echo response  POST /api/echo - Echoes back POST data $ curl http://localhost:8888/api/statusAn unhandled exception occurred at $00000000004197CF:EStackOverflow: Stack overflow or stack misalignment  $00000000004197CF  $00007F3490FE91AC  $FFFE7BFCFFFE7B94 Heap dump by heaptrc unit of "./microrestserver"330 memory blocks allocated : 38636/38720327 memory blocks freed     : 38436/385203 unfreed memory blocks : 200True heap size : 196608True free heap : 195744Should be : 195832Call trace for block $00007F348EF22100 size 128  $00000000004158B0  $00000000004197CF  $00007F3490FE91ACCall trace for block $00007F349102C600 size 40  $00000000004158B0  $00000000004197CF  $00007F3490FE91ACCall trace for block $00007F349102C500 size 32  $00000000004197CF  $00007F3490FE91ACcurl: (52) Empty reply from server[1]+  Uscita 217            ./microrestserver  Edit: version of libmicrohttpd in my system: libmicrohttpd-dev/stable,now 0.9.75-6 amd64 [installed] ---------------------------------------------------------------------- nomorelogic · 2025-05-06 08:09 Quote from: Fibonacci on May 04, 2025, 12:42:51 pmMy 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 · 2025-05-06 08:40 I will look into it, but my second version is running happily over here. What is your exact platform? ---------------------------------------------------------------------- nomorelogic · 2025-05-06 09:44 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][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;  url: PChar; method: PChar; version: PChar; upload_data: PChar;  upload_data_size: PCsize_t; ptr: PPointer): cint; cdecl;  Mine declaration: Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i   function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;    url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;    con_cls: PPointer): cint; cdecl; ---------------------------------------------------------------------- nomorelogic · 2025-05-13 17:59 hello everyone it's been a while but I have managed to put together (almost) all the ideas that have been suggested to me in this thread. I plan to make improvements to this code so it is not excluded I will put here other evolutions of the code. In any case, this version is self-documented and working. There is something to improve in the shutdown, I hope to fix it as soon as possible. Code: Pascal  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i program restfulhttpserverthreadsafe; {$mode objfpc}{$H+}{$define THREADPOOL} { This code has been dedicated to the public domain.  You may use, modify, copy, or distribute this code freely, for any purpose, without any conditions or warranties.  Author: nomorelogic (Basso Marcello) Date: 13/05/2025  Special thanks to (https://forum.lazarus.freepascal.org/index.php/topic,71031.0.html): - Thaddy - Fibonacci  Abstract: this code is an intentionally simple version of a microservice for a restful json server is intended to be a basis for creating better servers} uses  cthreads,  Classes,  SysUtils,  cmem,  libmicrohttpd,  fpjson,  jsonparser,  syncobjs; const  PORT = 8888; type   TRestResponse = record    StatusCode: cuint;    ResText: string;  end;   TClientContextInfo = record    Playload_AsText: string;    PlayLoadIsJson: boolean;    PlayLoad_AsJson: TJSONObject;    Response: TRestResponse;  end;  PClientContextInfo = ^TClientContextInfo; var  ShouldStop: boolean = False;  Daemon: PMHD_Daemon;  StopLock: TCriticalSection;   function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;    url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;    con_cls: PPointer): cint; cdecl;  var    response: PMHD_Response;    jsonObj: TJSONObject;    con_info: PClientContextInfo;  begin    Result := 0;     // - - - - - - - - - - - - - - - - - - - - - - -    // first call    // the first invocation is used to initialize the    // context of the request; generally a data structure is created    // - - - - - - - - - - - - - - - - - - - - - - -    if con_cls^ = nil then    begin      // init client context      New(con_info);      con_info^.Playload_AsText := '';    con_info^.PlayLoadIsJson := False;      con_info^.PlayLoad_AsJson := nil;      con_info^.Response.StatusCode := MHD_HTTP_OK;      con_info^.Response.ResText := '';      con_cls^ := con_info;      Exit(MHD_YES);    end; // else begin     con_info := PClientContextInfo(con_cls^);      // - - - - - - - - - - - - - - - - - - - - - - -    // Handle POST data    // in the case of POST data: an additional callback    // is executed to allow the input data to be handled    // - - - - - - - - - - - - - - - - - - - - - - -    if (method = MHD_HTTP_METHOD_POST) and (upload_data_size^ <> 0) then    begin       SetString(con_info^.Playload_AsText, upload_data, upload_data_size^);       // Try to parse as JSON if content-type is application/json      if Assigned(MHD_lookup_connection_value(connection, MHD_HEADER_KIND,      MHD_HTTP_HEADER_CONTENT_TYPE)) then        if (Pos('application/json',          MHD_lookup_connection_value(connection, MHD_HEADER_KIND,          MHD_HTTP_HEADER_CONTENT_TYPE)) > 0) then        begin          if con_info^.Playload_AsText <> '' then          try            con_info^.PlayLoadIsJson := True;            con_info^.PlayLoad_AsJson :=              GetJSON(con_info^.Playload_AsText) as TJSONObject;        except            // on exception: return error            con_info^.Response.StatusCode := MHD_HTTP_INTERNAL_SERVER_ERROR;          con_info^.Response.ResText := '{"error": "Invalid JSON format"}';          end;         end;       upload_data_size^ := 0;    Exit(MHD_YES);     end; // <-- handle post data     // - - - - - - - - - - - - - - - - - - - - - - -    // last call    // response handler    // - - - - - - - - - - - - - - - - - - - - - - -    if con_info^.Response.StatusCode = MHD_HTTP_OK then    begin       if url = '/shutdown' then      begin        jsonObj := TJSONObject.Create;      jsonObj.Add('status', 'shutting down');        con_info^.Response.ResText := jsonObj.AsJSON;        jsonObj.Free;       // try gracefully stop        StopLock.Enter;        try          ShouldStop := True;        finally          StopLock.Leave;        end;       end      else if (url = '/api/status') and (method = 'GET') then      begin        jsonObj := TJSONObject.Create;        jsonObj.Add('status', 'ok');        jsonObj.Add('uptime', FormatDateTime('hh:nn:ss', Now));        con_info^.Response.ResText := jsonObj.AsJSON;        jsonObj.Free;      end      else if url = '/api/echo' then      begin        if method = 'GET' then        begin          con_info^.Response.ResText := '{"method": "GET", "data": "echo"}';        end        else if method = 'POST' then      begin          // do we have a playload?          if con_info^.PlayLoadIsJson then          begin            if Assigned(con_info^.PlayLoad_AsJson) then              con_info^.Response.ResText := con_info^.PlayLoad_AsJson.AsJSON;        end          else          begin            con_info^.Response.ResText := con_info^.Playload_AsText;          end;           // playload is empty?          if con_info^.Response.ResText = '' then          begin            con_info^.Response.StatusCode := MHD_HTTP_NO_CONTENT;            con_info^.Response.ResText := '{"error": "No data received"}';        end;        end;       end; // POST /api/echo     end; // <-- second call     // - - - - - - - - - - - - - - - - - - - - -    // response  // send respnse and free resources    // - - - - - - - - - - - - - - - - - - - - -    response := MHD_create_response_from_buffer(Length(con_info^.Response.ResText),    PChar(con_info^.Response.ResText), MHD_RESPMEM_MUST_COPY);    MHD_add_response_header(response, 'Content-Type', 'application/json');    Result := MHD_queue_response(connection, con_info^.Response.StatusCode, response);     // Free response    MHD_destroy_response(response);     // Free client context    if Assigned(con_info^.PlayLoad_AsJson) then      con_info^.PlayLoad_AsJson.Free;    Dispose(con_info);    con_cls^ := nil;   end; // function HandleRestfulRequest begin  // critical section  StopLock := TCriticalSection.Create;  try     // Create the MHD daemon with either thread pool OR thread-per-connection    // Choose one of these options:     // Option 1: Thread pool (fixed number of threads)    {$if Defined(THREADPOOL)}    Daemon := MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD or      MHD_USE_DEBUG, PORT, nil, nil, @HandleRestfulRequest,      nil, MHD_OPTION_THREAD_POOL_SIZE, 4, MHD_OPTION_END);     {$else thread_per_connection}    // Option 2: Thread-per-connection (one thread per request)    Daemon := MHD_start_daemon(      MHD_USE_THREAD_PER_CONNECTION or MHD_USE_DEBUG or MHD_USE_INTERNAL_POLLING_THREAD,      PORT,      nil,      nil,      @HandleRestfulRequest,      nil,      MHD_OPTION_END    );    {$ifend}     if Daemon = nil then    begin      WriteLn('Error starting HTTP server!');      Halt(1);    end;     WriteLn('rest server started at: http://localhost:', PORT);    WriteLn('Endpoints: /api/status (GET), /api/echo (POST), /shutdown');     // Loop server  while not ShouldStop do      Sleep(100);     MHD_stop_daemon(Daemon);    WriteLn('Server stopped.');  finally    StopLock.Free;  end;end. call example: Code: Bash  [Select][+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i echo "request: GET status"curl -X GET -H "Content-Type: application/json" http://localhost:8888/api/statusecho "<<" echo "request: GET echo"curl -X GET -H "Content-Type: application/json" http://localhost:8888/api/echoecho "<<" echo "request: POST echo"curl -X POST -H "Content-Type: application/json" -d '{"message": "hello from client"}' http://localhost:8888/api/echoecho "<<" echo "request: shutdown"curl -X GET -H "Content-Type: application/json" http://localhost:8888/shutdownecho "<<"   I am obviously interested in ideas and suggestions that would improve and/or complete this code. nomorelogic