event_and_thread.pp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. (*
  2. @Example: `event_and_thread`.
  3. @Description: Use event-driven for usual requests and threads to slowly requests.
  4. @Authors: Silvio Clecio and Gilson Nunes
  5. *)
  6. program event_and_thread;
  7. // Shows `event_and_thread` details on Linux:
  8. //
  9. // $ ps axo pid,ppid,rss,vsz,nlwp,cmd | grep 'event_and_thread'
  10. //
  11. // But if you prefer to see only the number of thread of `event_and_thread`:
  12. //
  13. // $ ps axo nlwp,cmd | grep 'event_and_thread'
  14. {$mode objfpc}{$H+}
  15. {$MACRO ON}
  16. {$DEFINE DEBUG}
  17. {.$DEFINE WAIT_CLIENTS_DISCONNECT}
  18. {$DEFINE TIMEOUT := 10}
  19. {.$DEFINE CONTINGENCY_CONTROL}
  20. {$IF DEFINED(CONTINGENCY_CONTROL)}
  21. {$DEFINE MAX_THREAD_COUNT := 2}
  22. {$ENDIF}
  23. uses
  24. {$IFDEF UNIX}
  25. cthreads, BaseUnix,
  26. {$ELSE}
  27. Sockets,
  28. {$ENDIF}
  29. Classes, SysUtils, cutils, libmicrohttpd;
  30. procedure MHD_socket_close(fd: cint);
  31. begin
  32. {$IFDEF UNIX}
  33. FpClose(fd);
  34. {$ELSE}
  35. CloseSocket(fd);
  36. {$ENDIF}
  37. end;
  38. const
  39. PORT = 8888;
  40. var
  41. _threads: TFPList;
  42. _mutex: TRTLCriticalSection;
  43. type
  44. { TConnectionHandler }
  45. TConnectionHandler = packed record
  46. Connection: PMHD_Connection;
  47. Url: Pcchar;
  48. end;
  49. { TSlothThread }
  50. TSlothThread = class(TThread)
  51. private
  52. FHandler: TConnectionHandler;
  53. protected
  54. procedure Execute; override;
  55. public
  56. constructor Create(AHandler: TConnectionHandler);
  57. destructor Destroy; override;
  58. end;
  59. { TSlothThread }
  60. constructor TSlothThread.Create(AHandler: TConnectionHandler);
  61. begin
  62. inherited Create(True);
  63. FreeOnTerminate := True;
  64. FHandler := AHandler;
  65. end;
  66. destructor TSlothThread.Destroy;
  67. begin
  68. _threads.Remove(Self);
  69. inherited Destroy;
  70. end;
  71. procedure TSlothThread.Execute;
  72. const
  73. page: AnsiString =
  74. '<html><body>I''m a sloth, and my URL is "%s". T: %s</body></html>';
  75. var
  76. i: Byte;
  77. s: AnsiString;
  78. response: PMHD_Response;
  79. begin
  80. for i := 1 to TIMEOUT do
  81. begin
  82. if Terminated then
  83. Break;
  84. Sleep(1000);
  85. end;
  86. if not Terminated then
  87. begin
  88. s := Format(page, [FHandler.Url, DateTimeToStr(Now)]);
  89. response := MHD_create_response_from_buffer(Length(s), Pointer(s),
  90. MHD_RESPMEM_MUST_COPY);
  91. MHD_queue_response(FHandler.Connection, MHD_HTTP_OK, response);
  92. MHD_resume_connection(FHandler.Connection);
  93. MHD_destroy_response(response);
  94. end;
  95. end;
  96. { daemon }
  97. function RequestHandler(cls: Pointer; connection: PMHD_Connection;
  98. url: Pcchar; method: Pcchar; version: Pcchar; upload_data: Pcchar;
  99. upload_data_size: Psize_t; ptr: PPointer): cint; cdecl;
  100. const
  101. page = '<html><body>Hello world! T: %s</body></html>';
  102. {$IF DEFINED(CONTINGENCY_CONTROL)}
  103. busy_page: Pcchar = '<html><body>The server is busy. :-(</body></html>';
  104. {$ENDIF}
  105. var
  106. s: string;
  107. ret: cint;
  108. thr: TThread;
  109. response: PMHD_Response;
  110. handler: TConnectionHandler;
  111. begin
  112. if method <> 'GET' then
  113. Exit(MHD_NO);
  114. { By Gilson Nunes:
  115. "The connection state for first call is `MHD_CONNECTION_HEADERS_PROCESSED`
  116. and `MHD_CONNECTION_FOOTERS_RECEIVED` for the next, so the flag below
  117. ensures that the response will be delivered to the client after `MHD`
  118. finish all the request processing." }
  119. if not Assigned(ptr^) then
  120. begin
  121. ptr^ := Pointer(1);
  122. Exit(MHD_YES);
  123. end;
  124. ptr^ := nil;
  125. if (strcomp(url, '/sloth1') = 0) or (strcomp(url, '/sloth2') = 0) then
  126. begin
  127. {$IF DEFINED(CONTINGENCY_CONTROL)}
  128. if _threads.Count = MAX_THREAD_COUNT then
  129. begin
  130. response := MHD_create_response_from_buffer(Length(busy_page),
  131. busy_page, MHD_RESPMEM_PERSISTENT);
  132. ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  133. MHD_destroy_response(response);
  134. Exit(ret);
  135. end;
  136. {$ENDIF}
  137. MHD_suspend_connection(connection);
  138. handler.Connection := connection;
  139. handler.Url := url;
  140. thr := TSlothThread.Create(handler);
  141. EnterCriticalsection(_mutex);
  142. try
  143. _threads.Add(thr);
  144. finally
  145. LeaveCriticalsection(_mutex);
  146. end;
  147. thr.Start;
  148. Result := MHD_YES;
  149. end
  150. else
  151. begin
  152. s := Format(page, [DateTimeToStr(Now)]);
  153. response := MHD_create_response_from_buffer(Length(s), Pointer(s),
  154. MHD_RESPMEM_MUST_COPY);
  155. ret := MHD_queue_response(connection, MHD_HTTP_OK, response);
  156. MHD_destroy_response(response);
  157. Result := ret;
  158. end;
  159. end;
  160. var
  161. _daemon: PMHD_Daemon;
  162. procedure StopServer;
  163. var
  164. i: Integer;
  165. thr: TThread;
  166. sckt: MHD_socket;
  167. connections: PMHD_DaemonInfo;
  168. begin
  169. sckt := MHD_quiesce_daemon(_daemon);
  170. {$IFDEF MSWINDOWS}
  171. if LongWord(sckt) <> MHD_INVALID_SOCKET then
  172. {$ELSE}
  173. if sckt <> MHD_INVALID_SOCKET then
  174. {$ENDIF}
  175. MHD_socket_close(sckt);
  176. EnterCriticalsection(_mutex);
  177. try
  178. WriteLn('Threads: ', _threads.Count);
  179. for i := Pred(_threads.Count) downto 0 do
  180. begin
  181. thr := TThread(_threads[i]);
  182. WriteLn('Finishing thread $', HexStr(thr), ' ...');
  183. if Assigned(thr) then
  184. thr.Terminate;
  185. end;
  186. while _threads.Count > 0 do
  187. Sleep(500);
  188. finally
  189. LeaveCriticalsection(_mutex);
  190. end;
  191. connections := MHD_get_daemon_info(_daemon, MHD_DAEMON_INFO_CURRENT_CONNECTIONS);
  192. if Assigned(connections) then
  193. begin
  194. WriteLn('Connections: ', connections^.num_connections);
  195. {$IFDEF WAIT_CLIENTS_DISCONNECT}
  196. while True do
  197. begin
  198. if connections^.num_connections = 0 then
  199. Break;
  200. Sleep(500);
  201. end;
  202. {$ENDIF}
  203. end;
  204. MHD_stop_daemon(_daemon);
  205. WriteLn('Bye!');
  206. end;
  207. procedure SigProc(sig: cint); cdecl;
  208. begin
  209. WriteLn;
  210. StopServer;
  211. FreeAndNil(_threads);
  212. Halt;
  213. end;
  214. begin
  215. InitCriticalSection(_mutex);
  216. _threads := TFPList.Create;
  217. try
  218. _daemon := MHD_start_daemon(MHD_USE_SELECT_INTERNALLY or
  219. MHD_USE_SUSPEND_RESUME or MHD_USE_DEBUG,
  220. PORT, nil, nil, @RequestHandler, nil,
  221. {$IF DEFINED(CONTINGENCY_CONTROL)}
  222. MHD_OPTION_THREAD_POOL_SIZE, cuint(MAX_THREAD_COUNT),
  223. {$ENDIF}
  224. MHD_OPTION_CONNECTION_TIMEOUT, cuint(TIMEOUT + 1),
  225. MHD_OPTION_END);
  226. if not Assigned(_daemon) then
  227. Halt(1);
  228. signal(SIGINT, @SigProc);
  229. {$IFDEF MSWINDOWS}
  230. signal(SIGBREAK, @SigProc);
  231. {$ELSE}
  232. signal(SIGTERM, @SigProc);
  233. {$ENDIF}
  234. WriteLn('HTTP server running. Press [Ctrl+C] to stop the server ...');
  235. while Assigned(_daemon) do
  236. Sleep(100);
  237. finally
  238. FreeAndNil(_threads);
  239. DoneCriticalsection(_mutex);
  240. end;
  241. end.