BrookHTTPRequest.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  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. FIsUploading: Boolean;
  66. FClient: Pointer;
  67. FTLSSession: Pointer;
  68. FHandle: Psg_httpreq;
  69. function GetIP: string;
  70. function GetPaths: TArray<string>; {$IFNDEF DEBUG}inline;{$ENDIF}
  71. function GetContentType: string; {$IFNDEF DEBUG}inline;{$ENDIF}
  72. function GetReferer: string; {$IFNDEF DEBUG}inline;{$ENDIF}
  73. function GetUserAgent: string; {$IFNDEF DEBUG}inline;{$ENDIF}
  74. protected
  75. class procedure DoRequestIsolatedProcCallback(Acls: Pcvoid;
  76. Areq: Psg_httpreq; Ares: Psg_httpres); cdecl; static;
  77. {$IFNDEF FPC}
  78. class procedure DoRequestIsolatedAnonymousProcCallback(Acls: Pcvoid;
  79. Areq: Psg_httpreq; Ares: Psg_httpres); cdecl; static;
  80. {$ENDIF}
  81. class function CreateRequest(AHandle: Pointer): TBrookHTTPRequest; virtual;
  82. class function CreateResponse(AHandle: Pointer): TBrookHTTPResponse; virtual;
  83. function CreateUploads(AHandle: Pointer): TBrookHTTPUploads; virtual;
  84. function CreateHeaders(AHandle: Pointer): TBrookStringMap; virtual;
  85. function CreateCookies(AHandle: Pointer): TBrookStringMap; virtual;
  86. function CreateParams(AHandle: Pointer): TBrookStringMap; virtual;
  87. function CreateFields(AHandle: Pointer): TBrookStringMap; virtual;
  88. function CreatePayload(AHandle: Pointer): TBrookString; virtual;
  89. procedure HandleRequestError(ARequest: TBrookHTTPRequest;
  90. AResponse: TBrookHTTPResponse; AException: Exception);
  91. procedure DoRequestError(ASender: TObject; ARequest: TBrookHTTPRequest;
  92. AResponse: TBrookHTTPResponse; AException: Exception); virtual;
  93. function GetHandle: Pointer; override;
  94. function GetUserData: Pointer; virtual;
  95. procedure SetUserData(AValue: Pointer); virtual;
  96. public
  97. { Creates an instance of @code(TBrookHTTPRequest).
  98. @param(AHandle[in] Request handle.) }
  99. constructor Create(AHandle: Pointer); virtual;
  100. { Frees an instance of @code(TBrookHTTPRequest). }
  101. destructor Destroy; override;
  102. { Checks if the HTTP method is @code(POST), @code(PUT), @code(DELETE) or
  103. @code(OPTIONS). }
  104. function IsPost: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  105. { Checks if current path refers to @code('/favicon.ico'). }
  106. function IsFavicon: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  107. { Checks if request contains a valid TLS session. }
  108. function IsSecure: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  109. { Checks if the HTTP method is @code(HEAD) or @code(GET). }
  110. function IsCachable: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  111. { Checks if the request was done by an Ajax client. }
  112. function IsXhr: Boolean; {$IFNDEF DEBUG}inline;{$ENDIF}
  113. { Isolates a request from the main event loop to an own dedicated thread,
  114. bringing it back when the request finishes.
  115. @param(AProc[in] Procedure to handle requests and responses isolated from
  116. the main event loop.)
  117. @param(AUserData[in] User-defined data.) }
  118. procedure Isolate(AProc: TBrookHTTPRequestIsolatedProc;
  119. AUserData: Pointer = nil);{$IFNDEF FPC}overload;{$ENDIF}virtual;
  120. {$IFNDEF FPC}
  121. { Isolates a request from the main event loop to an own dedicated thread,
  122. bringing it back when the request finishes.
  123. @param(AProc[in] Anonymous Procedure to handle requests and responses
  124. isolated from the main event loop.)
  125. @param(AUserData[in] User-defined data.) }
  126. procedure Isolate(const AProc: TBrookHTTPRequestIsolatedAnonymousProc;
  127. AUserData: Pointer = nil); overload; virtual;
  128. {$ENDIF}
  129. { Reference to the server instance. }
  130. property ServerHandle: Pointer read FServerHandle;
  131. { Hash table containing the request headers. }
  132. property Headers: TBrookStringMap read FHeaders;
  133. { Hash table containing the request cookies. }
  134. property Cookies: TBrookStringMap read FCookies;
  135. { Hash table containing the request parameters (query-string). }
  136. property Params: TBrookStringMap read FParams;
  137. { Hash table containing the request fields (HTML form fields). }
  138. property Fields: TBrookStringMap read FFields;
  139. { String buffer containing the request payload. }
  140. property Payload: TBrookString read FPayload;
  141. { Contains the requested HTTP version. }
  142. property Version: string read FVersion;
  143. { Contains the requested HTTP method. }
  144. property Method: string read FMethod;
  145. { Contains the path component. }
  146. property Path: string read FPath;
  147. { Contains the client IP. }
  148. property IP: string read GetIP;
  149. { Contains the requested Content-Type. }
  150. property ContentType: string read GetContentType;
  151. { Contains the client User-Agent. }
  152. property UserAgent: string read GetUserAgent;
  153. { Where the request originated. }
  154. property Referer: string read GetReferer;
  155. { Contains the levels of the path component. }
  156. property Paths: TArray<string> read GetPaths;
  157. { Checks if the client is uploading data. }
  158. property IsUploading: Boolean read FIsUploading;
  159. { List of the uploaded files. }
  160. property Uploads: TBrookHTTPUploads read FUploads;
  161. { List of the uploaded files. This is an alias to property @code(Uploads). }
  162. property Files: TBrookHTTPUploads read FUploads;
  163. { Contains the socket handle of the client. }
  164. property Client: Pointer read FClient;
  165. { Contains the TLS session handle (GnuTLS). }
  166. property TLSSession: Pointer read FTLSSession;
  167. { User-defined data to be stored temporally in the request object. }
  168. property UserData: Pointer read GetUserData write SetUserData;
  169. end;
  170. implementation
  171. type
  172. { TBrookHTTPReqIsolatedProcHolder }
  173. TBrookHTTPReqIsolatedProcHolder<T> = class
  174. private
  175. FProc: T;
  176. FUserData: Pointer;
  177. public
  178. constructor Create(const AProc: T; AUserData: Pointer);
  179. property Proc: T read FProc;
  180. property UserData: Pointer read FUserData;
  181. end;
  182. { TBrookHTTPReqIsolatedProcHolder }
  183. constructor TBrookHTTPReqIsolatedProcHolder<T>.Create(const AProc: T;
  184. AUserData: Pointer);
  185. begin
  186. inherited Create;
  187. FProc := AProc;
  188. FUserData := AUserData;
  189. end;
  190. { TBrookHTTPRequest }
  191. constructor TBrookHTTPRequest.Create(AHandle: Pointer);
  192. begin
  193. inherited Create;
  194. FHandle := AHandle;
  195. FUploads := CreateUploads(sg_httpreq_uploads(FHandle));
  196. FHeaders := CreateHeaders(sg_httpreq_headers(FHandle));
  197. FCookies := CreateCookies(sg_httpreq_cookies(FHandle));
  198. FParams := CreateParams(sg_httpreq_params(FHandle));
  199. FFields := CreateFields(sg_httpreq_fields(FHandle));
  200. FPayload := CreatePayload(sg_httpreq_payload(FHandle));
  201. FServerHandle := sg_httpreq_srv(FHandle);
  202. FVersion := TMarshal.ToString(sg_httpreq_version(FHandle));
  203. FMethod := TMarshal.ToString(sg_httpreq_method(FHandle));
  204. FPath := TMarshal.ToString(sg_httpreq_path(FHandle));
  205. FIsUploading := sg_httpreq_is_uploading(FHandle);
  206. FClient := sg_httpreq_client(FHandle);
  207. if Assigned(sg_httpreq_tls_session) then
  208. FTLSSession := sg_httpreq_tls_session(FHandle);
  209. end;
  210. destructor TBrookHTTPRequest.Destroy;
  211. begin
  212. FUploads.Free;
  213. FHeaders.Free;
  214. FCookies.Free;
  215. FParams.Free;
  216. FFields.Free;
  217. FPayload.Free;
  218. inherited Destroy;
  219. end;
  220. class procedure TBrookHTTPRequest.DoRequestIsolatedProcCallback(Acls: Pcvoid;
  221. Areq: Psg_httpreq; Ares: Psg_httpres);
  222. var
  223. VHolder: TBrookHTTPReqIsolatedProcHolder<TBrookHTTPRequestIsolatedProc>;
  224. VReq: TBrookHTTPRequest;
  225. VRes: TBrookHTTPResponse;
  226. begin
  227. VHolder := Acls;
  228. try
  229. VReq := CreateRequest(Areq);
  230. VRes := CreateResponse(Ares);
  231. try
  232. try
  233. VHolder.Proc(VReq, VRes, VHolder.UserData);
  234. except
  235. on E: Exception do
  236. VReq.HandleRequestError(VReq, VRes, E);
  237. end;
  238. finally
  239. VRes.Free;
  240. VReq.Free;
  241. end;
  242. finally
  243. VHolder.Free;
  244. end;
  245. end;
  246. {$IFNDEF FPC}
  247. class procedure TBrookHTTPRequest.DoRequestIsolatedAnonymousProcCallback(
  248. Acls: Pcvoid; Areq: Psg_httpreq; Ares: Psg_httpres);
  249. var
  250. VHolder: TBrookHTTPReqIsolatedProcHolder<
  251. TBrookHTTPRequestIsolatedAnonymousProc>;
  252. VReq: TBrookHTTPRequest;
  253. VRes: TBrookHTTPResponse;
  254. begin
  255. VHolder := Acls;
  256. try
  257. VReq := CreateRequest(Areq);
  258. VRes := CreateResponse(Ares);
  259. try
  260. try
  261. VHolder.Proc(VReq, VRes, VHolder.UserData);
  262. except
  263. on E: Exception do
  264. VReq.HandleRequestError(VReq, VRes, E);
  265. end;
  266. finally
  267. VRes.Free;
  268. VReq.Free;
  269. end;
  270. finally
  271. VHolder.Free;
  272. end;
  273. end;
  274. {$ENDIF}
  275. class function TBrookHTTPRequest.CreateRequest(
  276. AHandle: Pointer): TBrookHTTPRequest;
  277. begin
  278. Result := TBrookHTTPRequest.Create(AHandle);
  279. end;
  280. class function TBrookHTTPRequest.CreateResponse(
  281. AHandle: Pointer): TBrookHTTPResponse;
  282. begin
  283. Result := TBrookHTTPResponse.Create(AHandle);
  284. end;
  285. function TBrookHTTPRequest.CreateUploads(AHandle: Pointer): TBrookHTTPUploads;
  286. begin
  287. Result := TBrookHTTPUploads.Create(AHandle);
  288. end;
  289. function TBrookHTTPRequest.CreateHeaders(AHandle: Pointer): TBrookStringMap;
  290. begin
  291. Result := TBrookStringMap.Create(AHandle);
  292. Result.ClearOnDestroy := False;
  293. end;
  294. function TBrookHTTPRequest.CreateCookies(AHandle: Pointer): TBrookStringMap;
  295. begin
  296. Result := TBrookStringMap.Create(AHandle);
  297. Result.ClearOnDestroy := False;
  298. end;
  299. function TBrookHTTPRequest.CreateParams(AHandle: Pointer): TBrookStringMap;
  300. begin
  301. Result := TBrookStringMap.Create(AHandle);
  302. Result.ClearOnDestroy := False;
  303. end;
  304. function TBrookHTTPRequest.CreateFields(AHandle: Pointer): TBrookStringMap;
  305. begin
  306. Result := TBrookStringMap.Create(AHandle);
  307. Result.ClearOnDestroy := False;
  308. end;
  309. function TBrookHTTPRequest.CreatePayload(AHandle: Pointer): TBrookString;
  310. begin
  311. Result := TBrookString.Create(AHandle);
  312. end;
  313. procedure TBrookHTTPRequest.HandleRequestError(ARequest: TBrookHTTPRequest;
  314. AResponse: TBrookHTTPResponse; AException: Exception);
  315. begin
  316. AResponse.Reset;
  317. try
  318. DoRequestError(Self, ARequest, AResponse, AException);
  319. except
  320. on E: Exception do
  321. AResponse.Send(E.Message, BROOK_CT_TEXT_PLAIN, 500);
  322. end;
  323. end;
  324. {$IFDEF FPC}
  325. {$PUSH}{$WARN 5024 OFF}
  326. {$ENDIF}
  327. procedure TBrookHTTPRequest.DoRequestError(ASender: TObject; //FI:O804
  328. ARequest: TBrookHTTPRequest; //FI:O804
  329. AResponse: TBrookHTTPResponse;
  330. AException: Exception);
  331. begin
  332. AResponse.Send(AException.Message, BROOK_CT_TEXT_PLAIN, 500);
  333. end;
  334. {$IFDEF FPC}
  335. {$POP}
  336. {$ENDIF}
  337. function TBrookHTTPRequest.GetHandle: Pointer;
  338. begin
  339. Result := FHandle;
  340. end;
  341. function TBrookHTTPRequest.GetIP: string;
  342. var
  343. P: array[0..45] of cchar;
  344. C: Pcvoid;
  345. begin
  346. SgLib.Check;
  347. C := sg_httpreq_client(FHandle);
  348. if not Assigned(C) then
  349. Exit('');
  350. SgLib.CheckLastError(sg_ip(C, @P[0], SizeOf(P)));
  351. Result := TMarshal.ToString(@P[0]);
  352. end;
  353. function TBrookHTTPRequest.GetPaths: TArray<string>;
  354. begin
  355. Result := Path.Split(['/'], TStringSplitOptions.ExcludeEmpty);
  356. end;
  357. function TBrookHTTPRequest.GetContentType: string;
  358. begin
  359. Result := FHeaders.Get('Content-Type');
  360. end;
  361. function TBrookHTTPRequest.GetReferer: string;
  362. begin
  363. if not FHeaders.TryValue('Referer', Result) then
  364. Result := FHeaders.Get('Referrer');
  365. end;
  366. function TBrookHTTPRequest.GetUserAgent: string;
  367. begin
  368. Result := FHeaders.Get('User-Agent');
  369. end;
  370. function TBrookHTTPRequest.IsPost: Boolean;
  371. begin
  372. Result := Sagui.IsPost(FMethod);
  373. end;
  374. function TBrookHTTPRequest.IsFavicon: Boolean;
  375. begin
  376. Result := SameText(Path, '/favicon.ico');
  377. end;
  378. function TBrookHTTPRequest.IsSecure: Boolean;
  379. begin
  380. Result := Assigned(FTLSSession);
  381. end;
  382. function TBrookHTTPRequest.IsCachable: Boolean;
  383. begin
  384. Result := (FMethod = 'HEAD') or (FMethod = 'GET');
  385. end;
  386. function TBrookHTTPRequest.IsXhr: Boolean;
  387. begin
  388. Result := SameText(FHeaders.Get('X-Requested-With'), 'xmlhttprequest');
  389. end;
  390. procedure TBrookHTTPRequest.Isolate(AProc: TBrookHTTPRequestIsolatedProc;
  391. AUserData: Pointer);
  392. var
  393. VHolder: TBrookHTTPReqIsolatedProcHolder<TBrookHTTPRequestIsolatedProc>;
  394. begin
  395. SgLib.Check;
  396. VHolder := TBrookHTTPReqIsolatedProcHolder<
  397. TBrookHTTPRequestIsolatedProc>.Create(AProc, AUserData);
  398. try
  399. SgLib.CheckLastError(sg_httpreq_isolate(FHandle,
  400. DoRequestIsolatedProcCallback, VHolder));
  401. except
  402. VHolder.Free;
  403. raise;
  404. end;
  405. end;
  406. {$IFNDEF FPC}
  407. procedure TBrookHTTPRequest.Isolate(
  408. const AProc: TBrookHTTPRequestIsolatedAnonymousProc; AUserData: Pointer);
  409. var
  410. VHolder: TBrookHTTPReqIsolatedProcHolder<
  411. TBrookHTTPRequestIsolatedAnonymousProc>;
  412. begin
  413. SgLib.Check;
  414. VHolder := TBrookHTTPReqIsolatedProcHolder<
  415. TBrookHTTPRequestIsolatedAnonymousProc>.Create(AProc, AUserData);
  416. try
  417. SgLib.CheckLastError(sg_httpreq_isolate(FHandle,
  418. DoRequestIsolatedAnonymousProcCallback, VHolder));
  419. except
  420. VHolder.Free;
  421. raise;
  422. end;
  423. end;
  424. {$ENDIF}
  425. procedure TBrookHTTPRequest.SetUserData(AValue: Pointer);
  426. begin
  427. SgLib.Check;
  428. SgLib.CheckLastError(sg_httpreq_set_user_data(FHandle, AValue));
  429. end;
  430. function TBrookHTTPRequest.GetUserData: Pointer;
  431. begin
  432. SgLib.Check;
  433. Result := sg_httpreq_user_data(FHandle);
  434. end;
  435. end.