BrookHTTPResponse.pas 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. (* _ _
  2. * | |__ _ __ ___ ___ | | __
  3. * | '_ \| '__/ _ \ / _ \| |/ /
  4. * | |_) | | | (_) | (_) | <
  5. * |_.__/|_| \___/ \___/|_|\_\
  6. *
  7. * Microframework which helps to develop web Pascal applications.
  8. *
  9. * Copyright (c) 2012-2020 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 dispatches data to the client. }
  26. unit BrookHTTPResponse;
  27. {$I BrookDefines.inc}
  28. interface
  29. uses
  30. RTLConsts,
  31. SysConst,
  32. SysUtils,
  33. Classes,
  34. Platform,
  35. Marshalling,
  36. libsagui,
  37. BrookHandledClasses,
  38. BrookStringMap,
  39. BrookHTTPCookies;
  40. resourcestring
  41. { Error message @code('Invalid status code: <code>.'). }
  42. SBrookInvalidHTTPStatus = 'Invalid status code: %d.';
  43. { Error message @code('Response already sent.'). }
  44. SBrookResponseAlreadySent = 'Response already sent.';
  45. { Error message @code('Generic ZLib error.'). }
  46. SBrookZLibError = 'Generic ZLib error.';
  47. type
  48. { Handles exceptions related to response class. }
  49. EBrookHTTPResponse = class(Exception);
  50. { Class which dispatches headers, contents, binaries, files and other data to
  51. the client. }
  52. TBrookHTTPResponse = class(TBrookHandledPersistent)
  53. private
  54. FCookies: TBrookHTTPCookies;
  55. FHeaders: TBrookStringMap;
  56. FHandle: Psg_httpres;
  57. FCompressed: Boolean;
  58. procedure SetCookies(AValue: TBrookHTTPCookies);
  59. protected
  60. class function DoStreamRead(Acls: Pcvoid; Aoffset: cuint64_t; Abuf: Pcchar;
  61. Asize: csize_t): cssize_t; cdecl; static;
  62. class procedure DoStreamFree(Acls: Pcvoid); cdecl; static;
  63. class procedure CheckStatus(AStatus: Word); static; inline;
  64. class procedure CheckStream(AStream: TStream); static; inline;
  65. function CreateHeaders(AHandle: Pointer): TBrookStringMap; virtual;
  66. function CreateCookies(AOwner: TPersistent): TBrookHTTPCookies; virtual;
  67. function GetHandle: Pointer; override;
  68. procedure CheckAlreadySent(Aret: cint); inline;
  69. procedure CheckZLib(Aret: cint); inline;
  70. public
  71. { Creates an instance of @code(TBrookHTTPResponse).
  72. @param(AHandle[in] Request handle.) }
  73. constructor Create(AHandle: Pointer); virtual;
  74. { Frees an instance of @code(TBrookHTTPResponse). }
  75. destructor Destroy; override;
  76. { Sets server cookie to the response handle.
  77. @param(AName[in] Cookie name.)
  78. @param(AValue[in] Cookie value.) }
  79. procedure SetCookie(const AName, AValue: string); virtual;
  80. { Sends a string content to the client.
  81. @param(AValue[in] String to be sent.)
  82. @param(AContentType[in] Content type.)
  83. @param(AStatus[in] HTTP status code.) }
  84. procedure Send(const AValue, AContentType: string; AStatus: Word); virtual;
  85. { Sends a formatted string content to the client.
  86. @param(AFmt[in] Format string.)
  87. @param(AArgs[in] Arguments to compose the formatted string.)
  88. @param(AContentType[in] Content type.)
  89. @param(AStatus[in] HTTP status code.) }
  90. procedure SendFmt(const AFormat: string; const AArgs: array of const;
  91. const AContentType: string; AStatus: Word); virtual;
  92. { Sends a binary content to the client.
  93. @param(ABinary[in] Binary content to be sent.)
  94. @param(ASize[in] Content size.)
  95. @param(AContentType[in] Content type.)
  96. @param(AStatus[in] HTTP status code.) }
  97. procedure SendBinary(ABuffer: Pointer; ASize: NativeUInt;
  98. const AContentType: string; AStatus: Word); virtual;
  99. { Sends an array of Bytes content to the client.
  100. @param(ABytes[in] Array of Bytes to be sent.)
  101. @param(ASize[in] Content size.)
  102. @param(AContentType[in] Content type.)
  103. @param(AStatus[in] HTTP status code.) }
  104. procedure SendBytes(const ABytes: TBytes; ASize: NativeUInt;
  105. const AContentType: string; AStatus: Word); virtual;
  106. { Sends a file to the client.
  107. @param(ASize[in] Size of the file to be sent. Use zero to calculate
  108. automatically.)
  109. @param(AMaxSize[in] Maximum allowed file size. Use zero for no limit.)
  110. @param(AOffset[in] Offset to start reading from in the file to be sent.)
  111. @param(AFileName[in] Path of the file to be sent.)
  112. @param(ADownloaded[in] If @True it offers the file as download.)
  113. @param(AStatus[in] HTTP status code.) }
  114. procedure SendFile(ASize: NativeUInt; AMaxSize, AOffset: UInt64;
  115. const AFileName: TFileName; ADownloaded: Boolean; AStatus: Word); virtual;
  116. { Sends a stream to the client.
  117. @param(AStream[in] Stream to be sent.)
  118. @param(AFreed[in] @True frees the stream automatically as soon as it
  119. is sent.)
  120. @param(AStatus[in] HTTP status code.) }
  121. procedure SendStream(AStream: TStream; AFreed: Boolean;
  122. AStatus: Word); overload; virtual;
  123. { Sends a stream to the client. The stream is freed automatically as soon as
  124. it is sent.
  125. @param(AStream[in] Stream to be sent.)
  126. @param(AStatus[in] HTTP status code.) }
  127. procedure SendStream(AStream: TStream; AStatus: Word); overload; virtual;
  128. { Sends an HTTP status 204 to the client indicating the server has fulfilled
  129. the request, but does not need to return a content.
  130. @param(AContentType[in] Content type.) }
  131. procedure SendEmpty(const AContentType: string); overload; virtual;
  132. { Sends an HTTP status 204 to the client indicating the server has fulfilled
  133. the request, but does not need to return a content. }
  134. procedure SendEmpty; overload; virtual;
  135. { Sends a string content to the client and redirects it to a new location.
  136. @param(AValue[in] String to be sent.)
  137. @param(ADestination[in] Destination to which it will be redirected as soon
  138. as the content is sent.)
  139. @param(AContentType[in] Content type.)
  140. @param(AStatus[in] HTTP status code (must be >=300 and <=307).) }
  141. procedure SendAndRedirect(const AValue, ADestination, AContentType: string;
  142. AStatus: Word); overload; virtual;
  143. { Sends a string content to the client with HTTP status 302 and redirects it
  144. to a new location.
  145. @param(AValue[in] String to be sent.)
  146. @param(ADestination[in] Destination to which it will be redirected as soon
  147. as the content is sent.)
  148. @param(AContentType[in] Content type.) }
  149. procedure SendAndRedirect(const AValue, ADestination,
  150. AContentType: string); overload; virtual;
  151. { Offers a file as download.
  152. @param(AFileName[in] Path of the file to be sent.)
  153. @param(AStatus[in] HTTP status code.) }
  154. procedure Download(const AFileName: TFileName;
  155. AStatus: Word); overload; virtual;
  156. { Sends a file to be rendered.
  157. @param(AFileName[in] Path of the file to be sent.)
  158. @param(AStatus[in] HTTP status code.) }
  159. procedure Render(const AFileName: TFileName;
  160. AStatus: Word); overload; virtual;
  161. { Offers a file as download.
  162. @param(AFileName[in] Path of the file to be sent.) }
  163. procedure Download(const AFileName: TFileName); overload; virtual;
  164. { Sends a file to be rendered.
  165. @param(AFileName[in] Path of the file to be sent.) }
  166. procedure Render(const AFileName: TFileName); overload; virtual;
  167. { Clears all headers, cookies, statuses and internal buffers of the response
  168. object. }
  169. procedure Clear; virtual;
  170. { Determines if the content must be compressed while sending.
  171. The compression is done by the ZLib library using the DEFLATE compression
  172. algorithm. It uses the Gzip format when the content is a file. }
  173. property Compressed: Boolean read FCompressed write FCompressed;
  174. { Hash table containing the headers to be sent to the client. }
  175. property Headers: TBrookStringMap read FHeaders;
  176. { Cookies to be sent to the client. }
  177. property Cookies: TBrookHTTPCookies read FCookies write SetCookies;
  178. end;
  179. implementation
  180. constructor TBrookHTTPResponse.Create(AHandle: Pointer);
  181. begin
  182. inherited Create;
  183. FHandle := AHandle;
  184. FHeaders := CreateHeaders(sg_httpres_headers(FHandle));
  185. FCookies := CreateCookies(Self);
  186. end;
  187. destructor TBrookHTTPResponse.Destroy;
  188. var
  189. C: TBrookHTTPCookie;
  190. begin
  191. for C in FCookies do
  192. FHeaders.Add('Set-Cookie', C.ToString);
  193. FCookies.Free;
  194. FHeaders.Free;
  195. inherited Destroy;
  196. end;
  197. function TBrookHTTPResponse.GetHandle: Pointer;
  198. begin
  199. Result := FHandle;
  200. end;
  201. procedure TBrookHTTPResponse.CheckAlreadySent(Aret: cint);
  202. begin
  203. if Aret = EALREADY then
  204. raise EBrookHTTPResponse.Create(SBrookResponseAlreadySent);
  205. end;
  206. procedure TBrookHTTPResponse.CheckZLib(Aret: cint);
  207. begin
  208. if Aret < 0 then
  209. raise EBrookHTTPResponse.Create(SBrookZLibError);
  210. end;
  211. class procedure TBrookHTTPResponse.CheckStatus(AStatus: Word);
  212. begin
  213. if (AStatus < 100) or (AStatus > 599) then
  214. raise EArgumentException.CreateFmt(SBrookInvalidHTTPStatus, [AStatus]);
  215. end;
  216. class procedure TBrookHTTPResponse.CheckStream(AStream: TStream);
  217. begin
  218. if not Assigned(AStream) then
  219. raise EArgumentNilException.CreateFmt(SParamIsNil, ['AStream']);
  220. end;
  221. function TBrookHTTPResponse.CreateHeaders(AHandle: Pointer): TBrookStringMap;
  222. begin
  223. Result := TBrookStringMap.Create(AHandle);
  224. Result.ClearOnDestroy := False;
  225. end;
  226. function TBrookHTTPResponse.CreateCookies(AOwner: TPersistent): TBrookHTTPCookies;
  227. begin
  228. Result := TBrookHTTPCookies.Create(AOwner);
  229. end;
  230. {$IFDEF FPC}
  231. {$PUSH}{$WARN 5024 OFF}
  232. {$ENDIF}
  233. class function TBrookHTTPResponse.DoStreamRead(
  234. Acls: Pcvoid; Aoffset: cuint64_t; //FI:O804
  235. Abuf: Pcchar; Asize: csize_t): cssize_t;
  236. begin
  237. Result := TStream(Acls).Read(Abuf^, Asize);
  238. if Result = 0 then
  239. Exit(sg_eor(False));
  240. if Result < 0 then
  241. Result := sg_eor(True);
  242. end;
  243. {$IFDEF FPC}
  244. {$POP}
  245. {$ENDIF}
  246. class procedure TBrookHTTPResponse.DoStreamFree(Acls: Pcvoid);
  247. begin
  248. TStream(Acls).Free;
  249. end;
  250. procedure TBrookHTTPResponse.SetCookies(AValue: TBrookHTTPCookies);
  251. begin
  252. if AValue = FCookies then
  253. Exit;
  254. if Assigned(AValue) then
  255. FCookies.Assign(AValue)
  256. else
  257. FCookies.Clear;
  258. end;
  259. procedure TBrookHTTPResponse.SetCookie(const AName, AValue: string);
  260. var
  261. M: TMarshaller;
  262. begin
  263. SgLib.Check;
  264. SgLib.CheckLastError(sg_httpres_set_cookie(FHandle, M.ToCString(AName),
  265. M.ToCString(AValue)));
  266. end;
  267. procedure TBrookHTTPResponse.Send(const AValue, AContentType: string;
  268. AStatus: Word);
  269. var
  270. M: TMarshaller;
  271. R: cint;
  272. begin
  273. if FCompressed then
  274. begin
  275. R := sg_httpres_zsendbinary(FHandle, M.ToCString(AValue), Length(AValue),
  276. M.ToCString(AContentType), AStatus);
  277. CheckZLib(R);
  278. end
  279. else
  280. R := sg_httpres_sendbinary(FHandle, M.ToCString(AValue), Length(AValue),
  281. M.ToCString(AContentType), AStatus);
  282. CheckAlreadySent(R);
  283. SgLib.CheckLastError(R);
  284. end;
  285. procedure TBrookHTTPResponse.SendFmt(const AFormat: string;
  286. const AArgs: array of const; const AContentType: string; AStatus: Word);
  287. begin
  288. Send(Format(AFormat, AArgs), AContentType, AStatus);
  289. end;
  290. procedure TBrookHTTPResponse.SendBinary(ABuffer: Pointer; ASize: NativeUInt;
  291. const AContentType: string; AStatus: Word);
  292. var
  293. M: TMarshaller;
  294. R: cint;
  295. begin
  296. CheckStatus(AStatus);
  297. SgLib.Check;
  298. if FCompressed then
  299. begin
  300. R := sg_httpres_zsendbinary(FHandle, ABuffer, ASize,
  301. M.ToCString(AContentType), AStatus);
  302. CheckZLib(R);
  303. end
  304. else
  305. R := sg_httpres_sendbinary(FHandle, ABuffer, ASize,
  306. M.ToCString(AContentType), AStatus);
  307. CheckAlreadySent(R);
  308. SgLib.CheckLastError(R);
  309. end;
  310. procedure TBrookHTTPResponse.SendBytes(const ABytes: TBytes; ASize: NativeUInt;
  311. const AContentType: string; AStatus: Word);
  312. begin
  313. SendBinary(@ABytes[0], ASize, AContentType, AStatus);
  314. end;
  315. procedure TBrookHTTPResponse.SendFile(ASize: NativeUInt; AMaxSize,
  316. AOffset: UInt64; const AFileName: TFileName; ADownloaded: Boolean;
  317. AStatus: Word);
  318. var
  319. M: TMarshaller;
  320. R: cint;
  321. begin
  322. CheckStatus(AStatus);
  323. SgLib.Check;
  324. if FCompressed then
  325. begin
  326. R := sg_httpres_zsendfile(FHandle, ASize, AMaxSize, AOffset,
  327. M.ToCString(AFileName), ADownloaded, AStatus);
  328. CheckZLib(R);
  329. end
  330. else
  331. R := sg_httpres_sendfile(FHandle, ASize, AMaxSize, AOffset,
  332. M.ToCString(AFileName), ADownloaded, AStatus);
  333. CheckAlreadySent(R);
  334. if R = ENOENT then
  335. raise EFileNotFoundException.Create(SFileNotFound);
  336. SgLib.CheckLastError(R);
  337. end;
  338. procedure TBrookHTTPResponse.SendStream(AStream: TStream; AFreed: Boolean;
  339. AStatus: Word);
  340. var
  341. FCb: sg_free_cb;
  342. R: cint;
  343. begin
  344. CheckStream(AStream);
  345. CheckStatus(AStatus);
  346. SgLib.Check;
  347. if AFreed then
  348. FCb := DoStreamFree
  349. else
  350. FCb := nil;
  351. if FCompressed then
  352. begin
  353. R := sg_httpres_zsendstream(FHandle, DoStreamRead, AStream, FCb, AStatus);
  354. CheckZLib(R);
  355. end
  356. else
  357. R := sg_httpres_sendstream(FHandle, 0, DoStreamRead, AStream, FCb, AStatus);
  358. CheckAlreadySent(R);
  359. SgLib.CheckLastError(R);
  360. end;
  361. procedure TBrookHTTPResponse.SendStream(AStream: TStream; AStatus: Word);
  362. begin
  363. SendStream(AStream, True, AStatus);
  364. end;
  365. procedure TBrookHTTPResponse.SendEmpty(const AContentType: string);
  366. begin
  367. Clear;
  368. Send('', AContentType, 204);
  369. end;
  370. procedure TBrookHTTPResponse.SendEmpty;
  371. begin
  372. Clear;
  373. Send('', '', 204);
  374. end;
  375. procedure TBrookHTTPResponse.SendAndRedirect(const AValue, ADestination,
  376. AContentType: string; AStatus: Word);
  377. begin
  378. if (AStatus < 300) or (AStatus > 307) then
  379. raise EBrookHTTPResponse.CreateFmt(SBrookInvalidHTTPStatus, [AStatus]);
  380. FHeaders.AddOrSet('Location', ADestination);
  381. Send(AValue, AContentType, AStatus);
  382. end;
  383. procedure TBrookHTTPResponse.SendAndRedirect(const AValue, ADestination,
  384. AContentType: string);
  385. begin
  386. SendAndRedirect(AValue, ADestination, AContentType, 302);
  387. end;
  388. procedure TBrookHTTPResponse.Download(const AFileName: TFileName;
  389. AStatus: Word);
  390. var
  391. M: TMarshaller;
  392. R: cint;
  393. begin
  394. SgLib.Check;
  395. if FCompressed then
  396. begin
  397. R := sg_httpres_zdownload(FHandle, M.ToCString(AFileName), AStatus);
  398. CheckZLib(R);
  399. end
  400. else
  401. R := sg_httpres_download(FHandle, M.ToCString(AFileName), AStatus);
  402. CheckAlreadySent(R);
  403. if R = ENOENT then
  404. raise EFileNotFoundException.Create(SFileNotFound);
  405. SgLib.CheckLastError(R);
  406. end;
  407. procedure TBrookHTTPResponse.Download(const AFileName: TFileName);
  408. begin
  409. Download(AFileName, 200);
  410. end;
  411. procedure TBrookHTTPResponse.Render(const AFileName: TFileName;
  412. AStatus: Word);
  413. var
  414. M: TMarshaller;
  415. R: cint;
  416. begin
  417. SgLib.Check;
  418. if FCompressed then
  419. begin
  420. R := sg_httpres_zrender(FHandle, M.ToCString(AFileName), AStatus);
  421. CheckZLib(R);
  422. end
  423. else
  424. R := sg_httpres_render(FHandle, M.ToCString(AFileName), AStatus);
  425. CheckAlreadySent(R);
  426. if R = ENOENT then
  427. raise EFileNotFoundException.Create(SFileNotFound);
  428. SgLib.CheckLastError(R);
  429. end;
  430. procedure TBrookHTTPResponse.Render(const AFileName: TFileName);
  431. begin
  432. Render(AFileName, 200);
  433. end;
  434. procedure TBrookHTTPResponse.Clear;
  435. begin
  436. SgLib.Check;
  437. SgLib.CheckLastError(sg_httpres_clear(FHandle));
  438. end;
  439. end.