BrookHTTPRequest.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  1. (* _ _
  2. * | |__ _ __ ___ ___ | | __
  3. * | '_ \| '__/ _ \ / _ \| |/ /
  4. * | |_) | | | (_) | (_) | <
  5. * |_.__/|_| \___/ \___/|_|\_\
  6. *
  7. * Microframework which helps to develop web Pascal applications.
  8. *
  9. * Copyright (c) 2012-2021 Silvio Clecio <[email protected]>
  10. *
  11. * Brook framework is free software; you can redistribute it and/or
  12. * modify it under the terms of the GNU Lesser General Public
  13. * License as published by the Free Software Foundation; either
  14. * version 2.1 of the License, or (at your option) any later version.
  15. *
  16. * Brook framework is distributed in the hope that it will be useful,
  17. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  18. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  19. * Lesser General Public License for more details.
  20. *
  21. * You should have received a copy of the GNU Lesser General Public
  22. * License along with Brook framework; if not, write to the Free Software
  23. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  24. *)
  25. { Contains class which receives data sent by the client. }
  26. unit BrookHTTPRequest;
  27. {$I BrookDefines.inc}
  28. interface
  29. uses
  30. SysUtils,
  31. Marshalling,
  32. libsagui,
  33. BrookUtility,
  34. BrookHandledClasses,
  35. BrookExtra,
  36. BrookString,
  37. BrookStringMap,
  38. BrookHTTPUploads,
  39. BrookHTTPResponse;
  40. type
  41. TBrookHTTPRequest = class;
  42. { Procedure signature used to trigger isolated requests. }
  43. TBrookHTTPRequestIsolatedProc = procedure(ARequest: TBrookHTTPRequest;
  44. AResponse: TBrookHTTPResponse; AUserData: Pointer);
  45. {$IFNDEF FPC}
  46. { Procedure anonymous signature used to trigger isolated requests. }
  47. TBrookHTTPRequestIsolatedAnonymousProc = reference to procedure(
  48. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse;
  49. AUserData: Pointer);
  50. {$ENDIF}
  51. { Class which provides headers, cookies, query-string, fields, payloads,
  52. uploads and other data sent by the client. }
  53. TBrookHTTPRequest = class(TBrookHandledPersistent)
  54. private
  55. FUploads: TBrookHTTPUploads;
  56. FServerHandle: Pointer;
  57. FHeaders: TBrookStringMap;
  58. FCookies: TBrookStringMap;
  59. FParams: TBrookStringMap;
  60. FFields: TBrookStringMap;
  61. FPayload: TBrookString;
  62. FVersion: string;
  63. FMethod: string;
  64. FPath: string;
  65. FIsIsolated: Boolean;
  66. FIsUploading: Boolean;
  67. FClient: Pointer;
  68. FTLSSession: Pointer;
  69. FHandle: Psg_httpreq;
  70. function GetIP: string;
  71. function GetPaths: TArray<string>; {$IFNDEF DEBUG}inline;{$ENDIF}
  72. function GetContentType: string; {$IFNDEF DEBUG}inline;{$ENDIF}
  73. function GetReferer: string; {$IFNDEF DEBUG}inline;{$ENDIF}
  74. function GetUserAgent: string; {$IFNDEF DEBUG}inline;{$ENDIF}
  75. procedure SetIsIsolated(AValue: Boolean); {$IFNDEF DEBUG}inline;{$ENDIF}
  76. protected
  77. class procedure DoRequestIsolatedProcCallback(Acls: Pcvoid;
  78. Areq: Psg_httpreq; Ares: Psg_httpres); cdecl; static;
  79. {$IFNDEF FPC}
  80. class procedure DoRequestIsolatedAnonymousProcCallback(Acls: Pcvoid;
  81. Areq: Psg_httpreq; Ares: Psg_httpres); cdecl; static;
  82. {$ENDIF}
  83. class function CreateRequest(AHandle: Pointer): TBrookHTTPRequest; virtual;
  84. class function CreateResponse(AHandle: Pointer): TBrookHTTPResponse; virtual;
  85. function CreateUploads(AHandle: Pointer): TBrookHTTPUploads; virtual;
  86. function CreateHeaders(AHandle: Pointer): TBrookStringMap; virtual;
  87. function CreateCookies(AHandle: Pointer): TBrookStringMap; virtual;
  88. function CreateParams(AHandle: Pointer): TBrookStringMap; virtual;
  89. function CreateFields(AHandle: Pointer): TBrookStringMap; virtual;
  90. function CreatePayload(AHandle: Pointer): TBrookString; virtual;
  91. procedure HandleRequestError(ARequest: TBrookHTTPRequest;
  92. AResponse: TBrookHTTPResponse; AException: Exception);
  93. procedure DoRequestError(ASender: TObject; ARequest: TBrookHTTPRequest;
  94. AResponse: TBrookHTTPResponse; AException: Exception); virtual;
  95. function GetHandle: Pointer; override;
  96. function GetUserData: Pointer; virtual;
  97. procedure SetUserData(AValue: Pointer); virtual;
  98. public
  99. { Creates an instance of @code(TBrookHTTPRequest).
  100. @param(AHandle[in] Request handle.) }
  101. constructor Create(AHandle: Pointer); virtual;
  102. { Frees an instance of @code(TBrookHTTPRequest). }
  103. destructor Destroy; override;
  104. { Checks if the HTTP method is @code(POST), @code(PUT), @code(DELETE) or
  105. @code(OPTIONS). }
  106. function IsPost: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  107. { Checks if current path refers to @code('/favicon.ico'). }
  108. function IsFavicon: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  109. { Checks if request contains a valid TLS session. }
  110. function IsSecure: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  111. { Checks if the HTTP method is @code(HEAD) or @code(GET). }
  112. function IsCachable: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  113. { Checks if the request was done by an Ajax client. }
  114. function IsXhr: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  115. { Isolates a request from the main event loop to an own dedicated thread,
  116. bringing it back when the request finishes.
  117. @param(AProc[in] Procedure to handle requests and responses isolated from
  118. the main event loop.)
  119. @param(AUserData[in] User-defined data.) }
  120. procedure Isolate(AProc: TBrookHTTPRequestIsolatedProc;
  121. AUserData: Pointer = nil);{$IFNDEF FPC}overload;{$ENDIF}virtual;
  122. {$IFNDEF FPC}
  123. { Isolates a request from the main event loop to an own dedicated thread,
  124. bringing it back when the request finishes.
  125. @param(AProc[in] Anonymous Procedure to handle requests and responses
  126. isolated from the main event loop.)
  127. @param(AUserData[in] User-defined data.) }
  128. procedure Isolate(const AProc: TBrookHTTPRequestIsolatedAnonymousProc;
  129. AUserData: Pointer = nil); overload; virtual;
  130. {$ENDIF}
  131. { Reference to the server instance. }
  132. property ServerHandle: Pointer read FServerHandle;
  133. { Hash table containing the request headers. }
  134. property Headers: TBrookStringMap read FHeaders;
  135. { Hash table containing the request cookies. }
  136. property Cookies: TBrookStringMap read FCookies;
  137. { Hash table containing the request parameters (query-string). }
  138. property Params: TBrookStringMap read FParams;
  139. { Hash table containing the request fields (HTML form fields). }
  140. property Fields: TBrookStringMap read FFields;
  141. { String buffer containing the request payload. }
  142. property Payload: TBrookString read FPayload;
  143. { Contains the requested HTTP version. }
  144. property Version: string read FVersion;
  145. { Contains the requested HTTP method. }
  146. property Method: string read FMethod;
  147. { Contains the path component. }
  148. property Path: string read FPath;
  149. { Contains the client IP. }
  150. property IP: string read GetIP;
  151. { Contains the requested Content-Type. }
  152. property ContentType: string read GetContentType;
  153. { Contains the client User-Agent. }
  154. property UserAgent: string read GetUserAgent;
  155. { Where the request originated. }
  156. property Referer: string read GetReferer;
  157. { Contains the levels of the path component. }
  158. property Paths: TArray<string> read GetPaths;
  159. { Checks if the request was isolated from the main event loop to an own
  160. dedicated thread. }
  161. property IsIsolated: Boolean read FIsIsolated;
  162. { Checks if the client is uploading data. }
  163. property IsUploading: Boolean read FIsUploading;
  164. { List of the uploaded files. }
  165. property Uploads: TBrookHTTPUploads read FUploads;
  166. { List of the uploaded files. This is an alias to property @code(Uploads). }
  167. property Files: TBrookHTTPUploads read FUploads;
  168. { Contains the socket handle of the client. }
  169. property Client: Pointer read FClient;
  170. { Contains the TLS session handle (GnuTLS). }
  171. property TLSSession: Pointer read FTLSSession;
  172. { User-defined data to be stored temporally in the request object. }
  173. property UserData: Pointer read GetUserData write SetUserData;
  174. end;
  175. implementation
  176. type
  177. { TBrookHTTPReqIsolatedProcHolder }
  178. TBrookHTTPReqIsolatedProcHolder<T> = class
  179. private
  180. FProc: T;
  181. FUserData: Pointer;
  182. public
  183. constructor Create(const AProc: T; AUserData: Pointer);
  184. property Proc: T read FProc;
  185. property UserData: Pointer read FUserData;
  186. end;
  187. { TBrookHTTPReqIsolatedProcHolder }
  188. constructor TBrookHTTPReqIsolatedProcHolder<T>.Create(const AProc: T;
  189. AUserData: Pointer);
  190. begin
  191. inherited Create;
  192. FProc := AProc;
  193. FUserData := AUserData;
  194. end;
  195. { TBrookHTTPRequest }
  196. constructor TBrookHTTPRequest.Create(AHandle: Pointer);
  197. begin
  198. inherited Create;
  199. FHandle := AHandle;
  200. FUploads := CreateUploads(sg_httpreq_uploads(FHandle));
  201. FHeaders := CreateHeaders(sg_httpreq_headers(FHandle));
  202. FCookies := CreateCookies(sg_httpreq_cookies(FHandle));
  203. FParams := CreateParams(sg_httpreq_params(FHandle));
  204. FFields := CreateFields(sg_httpreq_fields(FHandle));
  205. FPayload := CreatePayload(sg_httpreq_payload(FHandle));
  206. FServerHandle := sg_httpreq_srv(FHandle);
  207. FVersion := TMarshal.ToString(sg_httpreq_version(FHandle));
  208. FMethod := TMarshal.ToString(sg_httpreq_method(FHandle));
  209. FPath := TMarshal.ToString(sg_httpreq_path(FHandle));
  210. FIsUploading := sg_httpreq_is_uploading(FHandle);
  211. FClient := sg_httpreq_client(FHandle);
  212. if Assigned(sg_httpreq_tls_session) then
  213. FTLSSession := sg_httpreq_tls_session(FHandle);
  214. end;
  215. destructor TBrookHTTPRequest.Destroy;
  216. begin
  217. FUploads.Free;
  218. FHeaders.Free;
  219. FCookies.Free;
  220. FParams.Free;
  221. FFields.Free;
  222. FPayload.Free;
  223. inherited Destroy;
  224. end;
  225. class procedure TBrookHTTPRequest.DoRequestIsolatedProcCallback(Acls: Pcvoid;
  226. Areq: Psg_httpreq; Ares: Psg_httpres);
  227. var
  228. VHolder: TBrookHTTPReqIsolatedProcHolder<TBrookHTTPRequestIsolatedProc>;
  229. VReq: TBrookHTTPRequest;
  230. VRes: TBrookHTTPResponse;
  231. begin
  232. VHolder := Acls;
  233. try
  234. VReq := CreateRequest(Areq);
  235. VRes := CreateResponse(Ares);
  236. try
  237. try
  238. VHolder.Proc(VReq, VRes, VHolder.UserData);
  239. except
  240. on E: Exception do
  241. VReq.HandleRequestError(VReq, VRes, E);
  242. end;
  243. finally
  244. VRes.Free;
  245. VReq.Free;
  246. end;
  247. finally
  248. VHolder.Free;
  249. end;
  250. end;
  251. {$IFNDEF FPC}
  252. class procedure TBrookHTTPRequest.DoRequestIsolatedAnonymousProcCallback(
  253. Acls: Pcvoid; Areq: Psg_httpreq; Ares: Psg_httpres);
  254. var
  255. VHolder: TBrookHTTPReqIsolatedProcHolder<
  256. TBrookHTTPRequestIsolatedAnonymousProc>;
  257. VReq: TBrookHTTPRequest;
  258. VRes: TBrookHTTPResponse;
  259. begin
  260. VHolder := Acls;
  261. try
  262. VReq := CreateRequest(Areq);
  263. VRes := CreateResponse(Ares);
  264. try
  265. try
  266. VHolder.Proc(VReq, VRes, VHolder.UserData);
  267. except
  268. on E: Exception do
  269. VReq.HandleRequestError(VReq, VRes, E);
  270. end;
  271. finally
  272. VRes.Free;
  273. VReq.Free;
  274. end;
  275. finally
  276. VHolder.Free;
  277. end;
  278. end;
  279. {$ENDIF}
  280. class function TBrookHTTPRequest.CreateRequest(
  281. AHandle: Pointer): TBrookHTTPRequest;
  282. begin
  283. Result := TBrookHTTPRequest.Create(AHandle);
  284. end;
  285. class function TBrookHTTPRequest.CreateResponse(
  286. AHandle: Pointer): TBrookHTTPResponse;
  287. begin
  288. Result := TBrookHTTPResponse.Create(AHandle);
  289. end;
  290. function TBrookHTTPRequest.CreateUploads(AHandle: Pointer): TBrookHTTPUploads;
  291. begin
  292. Result := TBrookHTTPUploads.Create(AHandle);
  293. end;
  294. function TBrookHTTPRequest.CreateHeaders(AHandle: Pointer): TBrookStringMap;
  295. begin
  296. Result := TBrookStringMap.Create(AHandle);
  297. Result.ClearOnDestroy := False;
  298. end;
  299. function TBrookHTTPRequest.CreateCookies(AHandle: Pointer): TBrookStringMap;
  300. begin
  301. Result := TBrookStringMap.Create(AHandle);
  302. Result.ClearOnDestroy := False;
  303. end;
  304. function TBrookHTTPRequest.CreateParams(AHandle: Pointer): TBrookStringMap;
  305. begin
  306. Result := TBrookStringMap.Create(AHandle);
  307. Result.ClearOnDestroy := False;
  308. end;
  309. function TBrookHTTPRequest.CreateFields(AHandle: Pointer): TBrookStringMap;
  310. begin
  311. Result := TBrookStringMap.Create(AHandle);
  312. Result.ClearOnDestroy := False;
  313. end;
  314. function TBrookHTTPRequest.CreatePayload(AHandle: Pointer): TBrookString;
  315. begin
  316. Result := TBrookString.Create(AHandle);
  317. end;
  318. procedure TBrookHTTPRequest.HandleRequestError(ARequest: TBrookHTTPRequest;
  319. AResponse: TBrookHTTPResponse; AException: Exception);
  320. begin
  321. AResponse.Reset;
  322. try
  323. DoRequestError(Self, ARequest, AResponse, AException);
  324. except
  325. on E: Exception do
  326. AResponse.Send(E.Message, BROOK_CT_TEXT_PLAIN, 500);
  327. end;
  328. end;
  329. {$IFDEF FPC}
  330. {$PUSH}{$WARN 5024 OFF}
  331. {$ENDIF}
  332. procedure TBrookHTTPRequest.DoRequestError(ASender: TObject; //FI:O804
  333. ARequest: TBrookHTTPRequest; //FI:O804
  334. AResponse: TBrookHTTPResponse;
  335. AException: Exception);
  336. begin
  337. AResponse.Send(AException.Message, BROOK_CT_TEXT_PLAIN, 500);
  338. end;
  339. {$IFDEF FPC}
  340. {$POP}
  341. {$ENDIF}
  342. function TBrookHTTPRequest.GetHandle: Pointer;
  343. begin
  344. Result := FHandle;
  345. end;
  346. procedure TBrookHTTPRequest.SetIsIsolated(AValue: Boolean);
  347. begin
  348. FIsIsolated := AValue;
  349. end;
  350. function TBrookHTTPRequest.GetIP: string;
  351. var
  352. P: array[0..45] of cchar;
  353. C: Pcvoid;
  354. begin
  355. SgLib.Check;
  356. C := sg_httpreq_client(FHandle);
  357. if not Assigned(C) then
  358. Exit('');
  359. SgLib.CheckLastError(sg_ip(C, @P[0], SizeOf(P)));
  360. Result := TMarshal.ToString(@P[0]);
  361. end;
  362. function TBrookHTTPRequest.GetPaths: TArray<string>;
  363. begin
  364. Result := Path.Split(['/'], TStringSplitOptions.ExcludeEmpty);
  365. end;
  366. function TBrookHTTPRequest.GetContentType: string;
  367. begin
  368. Result := FHeaders.Get('Content-Type');
  369. end;
  370. function TBrookHTTPRequest.GetReferer: string;
  371. begin
  372. if not FHeaders.TryValue('Referer', Result) then
  373. Result := FHeaders.Get('Referrer');
  374. end;
  375. function TBrookHTTPRequest.GetUserAgent: string;
  376. begin
  377. Result := FHeaders.Get('User-Agent');
  378. end;
  379. function TBrookHTTPRequest.IsPost: Boolean;
  380. begin
  381. Result := Sagui.IsPost(FMethod);
  382. end;
  383. function TBrookHTTPRequest.IsFavicon: Boolean;
  384. begin
  385. Result := SameText(Path, '/favicon.ico');
  386. end;
  387. function TBrookHTTPRequest.IsSecure: Boolean;
  388. begin
  389. Result := Assigned(FTLSSession);
  390. end;
  391. function TBrookHTTPRequest.IsCachable: Boolean;
  392. begin
  393. Result := (FMethod = 'HEAD') or (FMethod = 'GET');
  394. end;
  395. function TBrookHTTPRequest.IsXhr: Boolean;
  396. begin
  397. Result := SameText(FHeaders.Get('X-Requested-With'), 'xmlhttprequest');
  398. end;
  399. procedure TBrookHTTPRequest.Isolate(AProc: TBrookHTTPRequestIsolatedProc;
  400. AUserData: Pointer);
  401. var
  402. VHolder: TBrookHTTPReqIsolatedProcHolder<TBrookHTTPRequestIsolatedProc>;
  403. begin
  404. SgLib.Check;
  405. SetIsIsolated(True);
  406. VHolder := TBrookHTTPReqIsolatedProcHolder<
  407. TBrookHTTPRequestIsolatedProc>.Create(AProc, AUserData);
  408. try
  409. SgLib.CheckLastError(sg_httpreq_isolate(FHandle,
  410. DoRequestIsolatedProcCallback, VHolder));
  411. except
  412. VHolder.Free;
  413. raise;
  414. end;
  415. end;
  416. {$IFNDEF FPC}
  417. procedure TBrookHTTPRequest.Isolate(
  418. const AProc: TBrookHTTPRequestIsolatedAnonymousProc; AUserData: Pointer);
  419. var
  420. VHolder: TBrookHTTPReqIsolatedProcHolder<
  421. TBrookHTTPRequestIsolatedAnonymousProc>;
  422. begin
  423. SgLib.Check;
  424. SetIsIsolated(True);
  425. VHolder := TBrookHTTPReqIsolatedProcHolder<
  426. TBrookHTTPRequestIsolatedAnonymousProc>.Create(AProc, AUserData);
  427. try
  428. SgLib.CheckLastError(sg_httpreq_isolate(FHandle,
  429. DoRequestIsolatedAnonymousProcCallback, VHolder));
  430. except
  431. VHolder.Free;
  432. raise;
  433. end;
  434. end;
  435. {$ENDIF}
  436. procedure TBrookHTTPRequest.SetUserData(AValue: Pointer);
  437. begin
  438. SgLib.Check;
  439. SgLib.CheckLastError(sg_httpreq_set_user_data(FHandle, AValue));
  440. end;
  441. function TBrookHTTPRequest.GetUserData: Pointer;
  442. begin
  443. SgLib.Check;
  444. Result := sg_httpreq_user_data(FHandle);
  445. end;
  446. end.