Recent

Author Topic: libmicrohttpd simple example  (Read 1506 times)

nomorelogic

  • Full Member
  • ***
  • Posts: 192
Re: libmicrohttpd simple example
« Reply #15 on: May 13, 2025, 07:59:38 pm »
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][+][-]
  1. program restfulhttpserverthreadsafe;
  2.  
  3. {$mode objfpc}{$H+}
  4. {$define THREADPOOL}
  5.  
  6. {
  7.  This code has been dedicated to the public domain.
  8.  
  9.  You may use, modify, copy, or distribute this code freely,
  10.  for any purpose, without any conditions or warranties.
  11.  
  12.  Author: nomorelogic (Basso Marcello)
  13.  Date: 13/05/2025
  14.  
  15.  Special thanks to (https://forum.lazarus.freepascal.org/index.php/topic,71031.0.html):
  16.  - Thaddy
  17.  - Fibonacci
  18.  
  19.  Abstract:
  20.  this code is an intentionally simple version of a microservice for a restful json server
  21.  is intended to be a basis for creating better servers
  22. }
  23.  
  24. uses
  25.   cthreads,
  26.   Classes,
  27.   SysUtils,
  28.   cmem,
  29.   libmicrohttpd,
  30.   fpjson,
  31.   jsonparser,
  32.   syncobjs;
  33.  
  34. const
  35.   PORT = 8888;
  36.  
  37. type
  38.  
  39.   TRestResponse = record
  40.     StatusCode: cuint;
  41.     ResText: string;
  42.   end;
  43.  
  44.   TClientContextInfo = record
  45.     Playload_AsText: string;
  46.     PlayLoadIsJson: boolean;
  47.     PlayLoad_AsJson: TJSONObject;
  48.     Response: TRestResponse;
  49.   end;
  50.   PClientContextInfo = ^TClientContextInfo;
  51.  
  52. var
  53.   ShouldStop: boolean = False;
  54.   Daemon: PMHD_Daemon;
  55.   StopLock: TCriticalSection;
  56.  
  57.   function HandleRestfulRequest(cls: Pointer; connection: PMHD_Connection;
  58.     url, method, version: pchar; upload_data: pchar; upload_data_size: pSize_t;
  59.     con_cls: PPointer): cint; cdecl;
  60.   var
  61.     response: PMHD_Response;
  62.     jsonObj: TJSONObject;
  63.     con_info: PClientContextInfo;
  64.   begin
  65.     Result := 0;
  66.  
  67.     // - - - - - - - - - - - - - - - - - - - - - - -
  68.     // first call
  69.     // the first invocation is used to initialize the
  70.     // context of the request; generally a data structure is created
  71.     // - - - - - - - - - - - - - - - - - - - - - - -
  72.     if con_cls^ = nil then
  73.     begin
  74.       // init client context
  75.       New(con_info);
  76.       con_info^.Playload_AsText := '';
  77.       con_info^.PlayLoadIsJson := False;
  78.       con_info^.PlayLoad_AsJson := nil;
  79.       con_info^.Response.StatusCode := MHD_HTTP_OK;
  80.       con_info^.Response.ResText := '';
  81.       con_cls^ := con_info;
  82.       Exit(MHD_YES);
  83.     end; // else begin
  84.  
  85.     con_info := PClientContextInfo(con_cls^);
  86.  
  87.  
  88.     // - - - - - - - - - - - - - - - - - - - - - - -
  89.     // Handle POST data
  90.     // in the case of POST data: an additional callback
  91.     // is executed to allow the input data to be handled
  92.     // - - - - - - - - - - - - - - - - - - - - - - -
  93.     if (method = MHD_HTTP_METHOD_POST) and (upload_data_size^ <> 0) then
  94.     begin
  95.  
  96.       SetString(con_info^.Playload_AsText, upload_data, upload_data_size^);
  97.  
  98.       // Try to parse as JSON if content-type is application/json
  99.       if Assigned(MHD_lookup_connection_value(connection, MHD_HEADER_KIND,
  100.         MHD_HTTP_HEADER_CONTENT_TYPE)) then
  101.         if (Pos('application/json',
  102.           MHD_lookup_connection_value(connection, MHD_HEADER_KIND,
  103.           MHD_HTTP_HEADER_CONTENT_TYPE)) > 0) then
  104.         begin
  105.           if con_info^.Playload_AsText <> '' then
  106.           try
  107.             con_info^.PlayLoadIsJson := True;
  108.             con_info^.PlayLoad_AsJson :=
  109.               GetJSON(con_info^.Playload_AsText) as TJSONObject;
  110.           except
  111.             // on exception: return error
  112.             con_info^.Response.StatusCode := MHD_HTTP_INTERNAL_SERVER_ERROR;
  113.             con_info^.Response.ResText := '{"error": "Invalid JSON format"}';
  114.           end;
  115.  
  116.         end;
  117.  
  118.       upload_data_size^ := 0;
  119.       Exit(MHD_YES);
  120.  
  121.     end; // <-- handle post data
  122.  
  123.     // - - - - - - - - - - - - - - - - - - - - - - -
  124.     // last call
  125.     // response handler
  126.     // - - - - - - - - - - - - - - - - - - - - - - -
  127.     if con_info^.Response.StatusCode = MHD_HTTP_OK then
  128.     begin
  129.  
  130.       if url = '/shutdown' then
  131.       begin
  132.         jsonObj := TJSONObject.Create;
  133.         jsonObj.Add('status', 'shutting down');
  134.         con_info^.Response.ResText := jsonObj.AsJSON;
  135.         jsonObj.Free;
  136.  
  137.         // try gracefully stop
  138.         StopLock.Enter;
  139.         try
  140.           ShouldStop := True;
  141.         finally
  142.           StopLock.Leave;
  143.         end;
  144.  
  145.       end
  146.       else if (url = '/api/status') and (method = 'GET') then
  147.       begin
  148.         jsonObj := TJSONObject.Create;
  149.         jsonObj.Add('status', 'ok');
  150.         jsonObj.Add('uptime', FormatDateTime('hh:nn:ss', Now));
  151.         con_info^.Response.ResText := jsonObj.AsJSON;
  152.         jsonObj.Free;
  153.       end
  154.       else if url = '/api/echo' then
  155.       begin
  156.         if method = 'GET' then
  157.         begin
  158.           con_info^.Response.ResText := '{"method": "GET", "data": "echo"}';
  159.         end
  160.         else if method = 'POST' then
  161.         begin
  162.           // do we have a playload?
  163.           if con_info^.PlayLoadIsJson then
  164.           begin
  165.             if Assigned(con_info^.PlayLoad_AsJson) then
  166.               con_info^.Response.ResText := con_info^.PlayLoad_AsJson.AsJSON;
  167.           end
  168.           else
  169.           begin
  170.             con_info^.Response.ResText := con_info^.Playload_AsText;
  171.           end;
  172.  
  173.           // playload is empty?
  174.           if con_info^.Response.ResText = '' then
  175.           begin
  176.             con_info^.Response.StatusCode := MHD_HTTP_NO_CONTENT;
  177.             con_info^.Response.ResText := '{"error": "No data received"}';
  178.           end;
  179.         end;
  180.  
  181.       end; // POST /api/echo
  182.  
  183.     end; // <-- second call
  184.  
  185.     // - - - - - - - - - - - - - - - - - - - - -
  186.     // response
  187.     // send respnse and free resources
  188.     // - - - - - - - - - - - - - - - - - - - - -
  189.     response := MHD_create_response_from_buffer(Length(con_info^.Response.ResText),
  190.       PChar(con_info^.Response.ResText), MHD_RESPMEM_MUST_COPY);
  191.     MHD_add_response_header(response, 'Content-Type', 'application/json');
  192.     Result := MHD_queue_response(connection, con_info^.Response.StatusCode, response);
  193.  
  194.     // Free response
  195.     MHD_destroy_response(response);
  196.  
  197.     // Free client context
  198.     if Assigned(con_info^.PlayLoad_AsJson) then
  199.       con_info^.PlayLoad_AsJson.Free;
  200.     Dispose(con_info);
  201.     con_cls^ := nil;
  202.  
  203.   end; // function HandleRestfulRequest
  204.  
  205. begin
  206.   // critical section
  207.   StopLock := TCriticalSection.Create;
  208.   try
  209.  
  210.     // Create the MHD daemon with either thread pool OR thread-per-connection
  211.     // Choose one of these options:
  212.  
  213.     // Option 1: Thread pool (fixed number of threads)
  214.     {$if Defined(THREADPOOL)}
  215.     Daemon := MHD_start_daemon(MHD_USE_INTERNAL_POLLING_THREAD or
  216.       MHD_USE_DEBUG, PORT, nil, nil, @HandleRestfulRequest,
  217.       nil, MHD_OPTION_THREAD_POOL_SIZE, 4, MHD_OPTION_END);
  218.  
  219.     {$else thread_per_connection}
  220.     // Option 2: Thread-per-connection (one thread per request)
  221.     Daemon := MHD_start_daemon(
  222.       MHD_USE_THREAD_PER_CONNECTION or MHD_USE_DEBUG or MHD_USE_INTERNAL_POLLING_THREAD,
  223.       PORT,
  224.       nil,
  225.       nil,
  226.       @HandleRestfulRequest,
  227.       nil,
  228.       MHD_OPTION_END
  229.     );
  230.     {$ifend}
  231.  
  232.     if Daemon = nil then
  233.     begin
  234.       WriteLn('Error starting HTTP server!');
  235.       Halt(1);
  236.     end;
  237.  
  238.     WriteLn('rest server started at: http://localhost:', PORT);
  239.     WriteLn('Endpoints: /api/status (GET), /api/echo (POST), /shutdown');
  240.  
  241.     // Loop server
  242.     while not ShouldStop do
  243.       Sleep(100);
  244.  
  245.     MHD_stop_daemon(Daemon);
  246.     WriteLn('Server stopped.');
  247.   finally
  248.     StopLock.Free;
  249.   end;
  250. end.


call example:
Code: Bash  [Select][+][-]
  1. echo "request: GET status"
  2. curl -X GET -H "Content-Type: application/json" http://localhost:8888/api/status
  3. echo "<<"
  4.  
  5. echo "request: GET echo"
  6. curl -X GET -H "Content-Type: application/json" http://localhost:8888/api/echo
  7. echo "<<"
  8.  
  9. echo "request: POST echo"
  10. curl -X POST -H "Content-Type: application/json" -d '{"message": "hello from client"}' http://localhost:8888/api/echo
  11. echo "<<"
  12.  
  13. echo "request: shutdown"
  14. curl -X GET -H "Content-Type: application/json" http://localhost:8888/shutdown
  15. echo "<<"
  16.  
  17.  


I am obviously interested in ideas and suggestions that would improve and/or complete this code.

nomorelogic

 

TinyPortal © 2005-2018