BrookURLRouter.pas 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036
  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 classes for fast URL routing. }
  26. unit BrookURLRouter;
  27. {$I BrookDefines.inc}
  28. interface
  29. uses
  30. RTLConsts,
  31. SysUtils,
  32. Classes,
  33. Platform,
  34. Marshalling,
  35. libsagui,
  36. BrookUtility,
  37. BrookHandledClasses,
  38. BrookStringMap,
  39. BrookExtra,
  40. BrookHTTPRequest,
  41. BrookHTTPResponse;
  42. resourcestring
  43. { Error message @code('Inactive router.'). }
  44. SBrookInactiveRouter = 'Inactive router.';
  45. { Error message @code('No routes defined.'). }
  46. SBrookNoRoutesDefined = 'No routes defined.';
  47. { Error message @code('<new-class>: pattern cannot be empty.'). }
  48. SBrookEmptyRoutePattern = '%s: pattern cannot be empty.';
  49. { Error message @code('<new-class>: pattern <pattern> already
  50. exists in <existing-class>.'). }
  51. SBrookRouteAlreadyExists = '%s: pattern ''%s'' already exists in ''%s''.';
  52. { Error message @code('Request method not allowed: <method>.'). }
  53. SBrookRequestMethodNotAllowed = 'Request method not allowed: %s.';
  54. { Error message @code('No routes defined.'). }
  55. SBrookRequestNoMethodDefined = 'No method(s) defined.';
  56. { Error message @code('Route not found: <route>.'). }
  57. SBrookRouteNotFound = 'Route not found: %s.';
  58. { Error message @code('A default route already exists.'). }
  59. SBrookDefaultRouteAlreadyExists = 'A default route already exists.';
  60. type
  61. TBrookURLRoute = class;
  62. TBrookURLRoutes = class;
  63. { Event signature used by @code(TBrookURLRoute) to notify a route matching. }
  64. TBrookURLRouteMatchEvent = procedure(ARoute: TBrookURLRoute) of object;
  65. { Event signature used by @code(TBrookURLRoute) to notify a client request. }
  66. TBrookURLRouteRequestEvent = procedure(ASender: TObject;
  67. ARoute: TBrookURLRoute; ARequest: TBrookHTTPRequest;
  68. AResponse: TBrookHTTPResponse) of object;
  69. { Event signature used by @code(TBrookURLRoute) to notify a request method
  70. matching. }
  71. TBrookURLRouteRequestMethodEvent = procedure(ASender: TObject;
  72. ARoute: TBrookURLRoute; ARequest: TBrookHTTPRequest;
  73. AResponse: TBrookHTTPResponse; var AAllowed: Boolean) of object;
  74. { Handles exceptions related to route classes. }
  75. EBrookURLRoute = class(Exception);
  76. { Class to represent a URL route item. }
  77. TBrookURLRoute = class(TBrookHandledCollectionItem)
  78. public const
  79. { Default route HTTP methods. }
  80. DefaultReqMethods = [rmGET, rmPOST];
  81. private
  82. FOnMath: TBrookURLRouteMatchEvent;
  83. FRoutes: TBrookURLRoutes;
  84. FVariables: TBrookStringMap;
  85. FHandle: Psg_route;
  86. Fvars: Psg_strmap;
  87. FPattern: string;
  88. FDefault: Boolean;
  89. FMethods: TBrookHTTPRequestMethods;
  90. FOnRequestMethod: TBrookURLRouteRequestMethodEvent;
  91. FOnRequest: TBrookURLRouteRequestEvent;
  92. function GetPattern: string;
  93. function GetPath: string;
  94. function GetRawPattern: string;
  95. function GetVariables: TBrookStringMap;
  96. function GetPCRE2Handle: Pointer;
  97. function GetUserData: Pointer;
  98. function IsDefaultStored: Boolean;
  99. procedure SetDefault(AValue: Boolean);
  100. procedure SetPattern(const AValue: string);
  101. function IsMethodsStored: Boolean;
  102. function GetSegments: TArray<string>;
  103. protected
  104. class procedure DoRouteCallback(Acls: Pcvoid;
  105. Aroute: Psg_route); cdecl; static;
  106. class function DoSegmentsIterCallback(Acls: Pcvoid; Aindex: cuint;
  107. const Asegment: Pcchar): cint; cdecl; static;
  108. class function DoVarsIterCallback(Acls: Pcvoid;
  109. const Aname: Pcchar; const Aval: Pcchar): cint; cdecl; static;
  110. function GetHandle: Pointer; override;
  111. procedure DoMatch(ARoute: TBrookURLRoute); virtual;
  112. procedure DoRequestMethod(ASender: TObject; ARoute: TBrookURLRoute;
  113. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse;
  114. var AAllowed: Boolean); virtual;
  115. procedure DoRequest(ASender: TObject; ARoute: TBrookURLRoute;
  116. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse); virtual;
  117. procedure HandleMatch(ARoute: TBrookURLRoute); virtual;
  118. procedure HandleRequest(ASender: TObject; ARoute: TBrookURLRoute;
  119. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse); virtual;
  120. function IsMethodAllowed(const AMethod: string): Boolean; virtual;
  121. procedure SendMethodNotAllowed(const AMethod: string;
  122. AResponse: TBrookHTTPResponse); virtual;
  123. procedure CheckMethods; {$IFNDEF DEBUG}inline;{$ENDIF}
  124. property Routes: TBrookURLRoutes read FRoutes;
  125. public
  126. { Creates an instance of @code(TBrookURLRoute).
  127. @param(ACollection[in] Routes list.) }
  128. constructor Create(ACollection: TCollection); override;
  129. { Frees an instance of @code(TBrookURLRoute). }
  130. destructor Destroy; override;
  131. { Checks if the route pattern is valid. }
  132. procedure Validate; {$IFNDEF DEBUG}inline;{$ENDIF}
  133. { Contains the PCRE2 instance. }
  134. property PCRE2Handle: Pointer read GetPCRE2Handle;
  135. { Contains all path segments (a.k.a. path levels). }
  136. property Segments: TArray<string> read GetSegments;
  137. { Contains all path variables (a.k.a. query-string parameters). }
  138. property Variables: TBrookStringMap read GetVariables;
  139. { Contains the raw route pattern. For example, given a pattern @code(/foo),
  140. the raw pattern is @code(^/foo$). }
  141. property RawPattern: string read GetRawPattern;
  142. { Contains the route path. }
  143. property Path: string read GetPath;
  144. { User-defined data to be stored temporarily in the route object. }
  145. property UserData: Pointer read GetUserData;
  146. published
  147. { Default route called if URL does not match any registered route. }
  148. property Default: Boolean read FDefault write SetDefault
  149. stored IsDefaultStored default False;
  150. { Pattern to find the route. It must be a valid regular expression in
  151. PCRE2 syntax. }
  152. property Pattern: string read GetPattern write SetPattern;
  153. { Allowed methods to find the route. }
  154. property Methods: TBrookHTTPRequestMethods read FMethods write FMethods
  155. stored IsMethodsStored default DefaultReqMethods;
  156. { Event triggered when the path matches the route pattern. }
  157. property OnMath: TBrookURLRouteMatchEvent read FOnMath write FOnMath;
  158. { Event triggered when the HTTP method matches a route allowed method. }
  159. property OnRequestMethod: TBrookURLRouteRequestMethodEvent
  160. read FOnRequestMethod write FOnRequestMethod;
  161. { Event triggered when a client requests the route. }
  162. property OnRequest: TBrookURLRouteRequestEvent read FOnRequest
  163. write FOnRequest;
  164. end;
  165. { Class-reference for @code(TBrookURLRoute). }
  166. TBrookURLRouteClass = class of TBrookURLRoute;
  167. { List enumerator for @code(TBrookURLRoutes). }
  168. TBrookURLRoutesEnumerator = class(TCollectionEnumerator)
  169. public
  170. { Get current route item. }
  171. function GetCurrent: TBrookURLRoute;
  172. { Current route item. }
  173. property Current: TBrookURLRoute read GetCurrent;
  174. end;
  175. { Handles exceptions related to routes classes. }
  176. EBrookURLRoutes = class(Exception);
  177. { Class to represent an list of URL routes. }
  178. TBrookURLRoutes = class(TBrookHandledOwnedCollection)
  179. private
  180. FHandle: Psg_route;
  181. procedure InternalLibUnloadEvent(ASender: TObject);
  182. protected
  183. function GetHandle: Pointer; override;
  184. class function GetRoutePattern(ARoute: TBrookURLRoute): string; virtual;
  185. class function GetRouteLabel: string; virtual;
  186. function GetItem(AIndex: Integer): TBrookURLRoute; virtual;
  187. procedure SetItem(AIndex: Integer; AValue: TBrookURLRoute); virtual;
  188. procedure InternalAdd(ARoute: TBrookURLRoute); virtual;
  189. procedure Prepare; virtual;
  190. procedure Unprepare; virtual;
  191. public
  192. { Creates an instance of @code(TBrookURLRoutes).
  193. @param(AOwner[in] Routes persistent.) }
  194. constructor Create(AOwner: TPersistent); virtual;
  195. { Frees an instance of @code(TBrookURLRoutes). }
  196. destructor Destroy; override;
  197. { Gets the default class for route item creation. }
  198. class function GetRouterClass: TBrookURLRouteClass; virtual;
  199. { Creates an enumerator to iterate the routes through @code(for..in). }
  200. function GetEnumerator: TBrookURLRoutesEnumerator;
  201. { Generates a new route pattern. }
  202. function NewPattern: string; virtual;
  203. { Adds a new item to the routes list.
  204. @returns(Route item.) }
  205. function Add: TBrookURLRoute; virtual;
  206. { Gets the first route in the routes list. }
  207. function First: TBrookURLRoute; virtual;
  208. { Gets the last route in the routes list. }
  209. function Last: TBrookURLRoute; virtual;
  210. { Gets the route index by its pattern. }
  211. function IndexOf(const APattern: string): Integer; virtual;
  212. { Finds a route in the routes list by its pattern.
  213. @param(APattern[in] Route name.) }
  214. function Find(const APattern: string): TBrookURLRoute; virtual;
  215. { Finds a default route in the routes list. }
  216. function FindDefault: TBrookURLRoute; virtual;
  217. { Removes a route from the routes list by its pattern.
  218. @param(APattern[in] Route name.) }
  219. function Remove(const APattern: string): Boolean; virtual;
  220. { Clears the routes list. }
  221. procedure Clear; virtual;
  222. { Gets/sets a route from/to the routes list by its index. }
  223. property Items[AIndex: Integer]: TBrookURLRoute read GetItem
  224. write SetItem; default;
  225. end;
  226. { Event signature used by @code(TBrookURLRouter) to handle routing. }
  227. TBrookURLRouterRouteEvent = procedure(ASender: TObject; const ARoute: string;
  228. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse) of object;
  229. { URL router component. }
  230. TBrookURLRouter = class(TBrookHandledComponent)
  231. private
  232. FLocker: TBrookLocker;
  233. FRoutes: TBrookURLRoutes;
  234. FHandle: Psg_router;
  235. FActive: Boolean;
  236. FStreamedActive: Boolean;
  237. FOnNotFound: TBrookURLRouterRouteEvent;
  238. FOnRoute: TBrookURLRouterRouteEvent;
  239. FOnActivate: TNotifyEvent;
  240. FOnDeactivate: TNotifyEvent;
  241. function GetItem(AIndex: Integer): TBrookURLRoute;
  242. function IsActiveStored: Boolean;
  243. procedure SetActive(AValue: Boolean);
  244. procedure SetItem(AIndex: Integer; AValue: TBrookURLRoute);
  245. procedure SetRoutes(AValue: TBrookURLRoutes);
  246. procedure InternalLibUnloadEvent(ASender: TObject);
  247. protected
  248. function CreateLocker: TBrookLocker; virtual;
  249. function CreateRoutes: TBrookURLRoutes; virtual;
  250. procedure Loaded; override;
  251. function GetHandle: Pointer; override;
  252. procedure DoRoute(ASender: TObject; const ARoute: string;
  253. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse); virtual;
  254. procedure DoNotFound(ASender: TObject; const ARoute: string;
  255. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse); virtual;
  256. procedure DoOpen; virtual;
  257. procedure DoClose; virtual;
  258. procedure CheckItems; {$IFNDEF DEBUG}inline;{$ENDIF}
  259. procedure CheckActive; {$IFNDEF DEBUG}inline;{$ENDIF}
  260. function DispatchRoute(const APath: string;
  261. AUserData: Pointer): Boolean; virtual;
  262. property Locker: TBrookLocker read FLocker;
  263. public
  264. { Creates an instance of @code(TBrookURLRouter).
  265. @param(AOwner[in] Owner component.) }
  266. constructor Create(AOwner: TComponent); override;
  267. { Destroys an instance of @code(TBrookURLRouter). }
  268. destructor Destroy; override;
  269. { Creates an enumerator to iterate the routes through @code(for..in). }
  270. function GetEnumerator: TBrookURLRoutesEnumerator;
  271. { Adds a new item to the routes list.
  272. @returns(Route item.) }
  273. function Add: TBrookURLRoute; {$IFNDEF DEBUG}inline;{$ENDIF}
  274. { Removes an item from the routes list by its pattern.
  275. @param(APattern[in] Route name.) }
  276. procedure Remove(const APattern: string); {$IFNDEF DEBUG}inline;{$ENDIF}
  277. { Clears the routes list. }
  278. procedure Clear; {$IFNDEF DEBUG}inline;{$ENDIF}
  279. { Enabled the router component. }
  280. procedure Open;
  281. { Disables the router component. }
  282. procedure Close;
  283. { Routes a request passing a given path.
  284. @param(ASender[in] Sender object.)
  285. @param(APath[in] Route path.)
  286. @param(ARequest[in] Request object.)
  287. @param(AResponse[in] Response object.) }
  288. procedure Route(ASender: TObject;
  289. const APath: string; ARequest: TBrookHTTPRequest;
  290. AResponse: TBrookHTTPResponse); overload; virtual;
  291. { Routes a request obtaining path from the request object.
  292. @param(ASender[in] Sender object.)
  293. @param(ARequest[in] Request object.)
  294. @param(AResponse[in] Response object.) }
  295. procedure Route(ASender: TObject; ARequest: TBrookHTTPRequest;
  296. AResponse: TBrookHTTPResponse); overload; virtual;
  297. { Gets/sets a route from/to the routes list by its index. }
  298. property Items[AIndex: Integer]: TBrookURLRoute read GetItem
  299. write SetItem; default;
  300. published
  301. { Enabled/disables the router component. }
  302. property Active: Boolean read FActive write SetActive stored IsActiveStored;
  303. { Available routes list. }
  304. property Routes: TBrookURLRoutes read FRoutes write SetRoutes;
  305. { Event triggered when the router dispatches a route. }
  306. property OnRoute: TBrookURLRouterRouteEvent read FOnRoute write FOnRoute;
  307. { Event triggered when a route is not found. }
  308. property OnNotFound: TBrookURLRouterRouteEvent read FOnNotFound
  309. write FOnNotFound;
  310. { Event triggered when the router component is enabled. }
  311. property OnActivate: TNotifyEvent read FOnActivate write FOnActivate;
  312. { Event triggered when the router component is disabled. }
  313. property OnDeactivate: TNotifyEvent read FOnDeactivate write FOnDeactivate;
  314. end;
  315. implementation
  316. type
  317. { TBrookURLRouteHolder }
  318. TBrookURLRouteHolder = record
  319. Locker: TBrookLocker;
  320. Request: TBrookHTTPRequest;
  321. Response: TBrookHTTPResponse;
  322. Sender: TObject;
  323. end;
  324. { TBrookURLRoute }
  325. constructor TBrookURLRoute.Create(ACollection: TCollection);
  326. begin
  327. inherited Create(ACollection);
  328. FVariables := TBrookStringMap.Create(@Fvars);
  329. if Assigned(ACollection) and (ACollection is TBrookURLRoutes) then
  330. begin
  331. FRoutes := ACollection as TBrookURLRoutes;
  332. FPattern := FRoutes.NewPattern;
  333. end
  334. else
  335. FPattern := '/';
  336. FMethods := DefaultReqMethods;
  337. end;
  338. destructor TBrookURLRoute.Destroy;
  339. begin
  340. FVariables.ClearOnDestroy := False;
  341. FVariables.Free;
  342. inherited Destroy;
  343. end;
  344. class procedure TBrookURLRoute.DoRouteCallback(Acls: Pcvoid; Aroute: Psg_route);
  345. var
  346. VRoute: TBrookURLRoute;
  347. begin
  348. VRoute := Acls;
  349. VRoute.FHandle := Aroute;
  350. VRoute.HandleMatch(VRoute);
  351. end;
  352. {$IFDEF FPC}
  353. {$PUSH}{$WARN 5024 OFF}
  354. {$ENDIF}
  355. class function TBrookURLRoute.DoSegmentsIterCallback(Acls: Pcvoid;
  356. Aindex: cuint; //FI:O804
  357. const Asegment: Pcchar): cint;
  358. var
  359. VSegments: ^TArray<string>;
  360. begin
  361. VSegments := Acls;
  362. VSegments^ := Concat(VSegments^, [TMarshal.ToString(Asegment)]);
  363. Result := 0;
  364. end;
  365. {$IFDEF FPC}
  366. {$POP}
  367. {$ENDIF}
  368. class function TBrookURLRoute.DoVarsIterCallback(Acls: Pcvoid;
  369. const Aname: Pcchar; const Aval: Pcchar): cint;
  370. begin
  371. TBrookStringMap(Acls).Add(TMarshal.ToString(Aname), TMarshal.ToString(Aval));
  372. Result := 0;
  373. end;
  374. procedure TBrookURLRoute.CheckMethods;
  375. begin
  376. if FMethods = [rmUnknown] then
  377. raise EBrookURLRoute.Create(SBrookRequestNoMethodDefined);
  378. end;
  379. function TBrookURLRoute.GetHandle: Pointer;
  380. begin
  381. Result := FHandle;
  382. end;
  383. function TBrookURLRoute.GetPCRE2Handle: Pointer;
  384. begin
  385. if not Assigned(FHandle) then
  386. Exit(nil);
  387. SgLib.Check;
  388. Result := sg_route_handle(FHandle);
  389. end;
  390. function TBrookURLRoute.GetSegments: TArray<string>;
  391. begin
  392. Result := nil;
  393. if not Assigned(FHandle) then
  394. Exit(nil);
  395. SgLib.Check;
  396. SgLib.CheckLastError(sg_route_segments_iter(FHandle, DoSegmentsIterCallback,
  397. @Result));
  398. end;
  399. function TBrookURLRoute.GetVariables: TBrookStringMap;
  400. begin
  401. Result := FVariables;
  402. if not Assigned(FHandle) then
  403. Exit;
  404. FVariables.Clear;
  405. SgLib.Check;
  406. SgLib.CheckLastError(sg_route_vars_iter(FHandle, DoVarsIterCallback,
  407. FVariables));
  408. end;
  409. function TBrookURLRoute.GetRawPattern: string;
  410. begin
  411. if not Assigned(FHandle) then
  412. begin
  413. if FPattern.IsEmpty then
  414. Exit('');
  415. Exit(Concat('^', FPattern, '$'));
  416. end;
  417. SgLib.Check;
  418. Result := TMarshal.ToString(sg_route_rawpattern(FHandle));
  419. end;
  420. function TBrookURLRoute.GetPattern: string;
  421. var
  422. P: Pcchar;
  423. begin
  424. if not Assigned(FHandle) then
  425. Exit(FPattern);
  426. SgLib.Check;
  427. P := sg_route_pattern(FHandle);
  428. try
  429. Result := TMarshal.ToString(P);
  430. finally
  431. sg_free(P);
  432. end;
  433. end;
  434. function TBrookURLRoute.GetPath: string;
  435. begin
  436. if not Assigned(FHandle) then
  437. Exit('');
  438. SgLib.Check;
  439. Result := TMarshal.ToString(sg_route_path(FHandle));
  440. end;
  441. function TBrookURLRoute.GetUserData: Pointer;
  442. begin
  443. if not Assigned(FHandle) then
  444. Exit(nil);
  445. SgLib.Check;
  446. Result := sg_route_user_data(FHandle);
  447. end;
  448. function TBrookURLRoute.IsDefaultStored: Boolean;
  449. begin
  450. Result := FDefault;
  451. end;
  452. procedure TBrookURLRoute.SetDefault(AValue: Boolean);
  453. begin
  454. if FDefault = AValue then
  455. Exit;
  456. if AValue and Assigned(FRoutes) and Assigned(FRoutes.FindDefault()) then
  457. raise EBrookURLRoute.Create(SBrookDefaultRouteAlreadyExists);
  458. FDefault := AValue;
  459. end;
  460. procedure TBrookURLRoute.SetPattern(const AValue: string);
  461. var
  462. RT: TBrookURLRoute;
  463. NP: string;
  464. begin
  465. if (AValue = FPattern) or (not Assigned(FRoutes)) then
  466. Exit;
  467. NP := Brook.FixPath(AValue);
  468. RT := FRoutes.Find(NP);
  469. if Assigned(RT) and (RT <> Self) then
  470. raise EBrookURLRoute.CreateFmt(SBrookRouteAlreadyExists,
  471. [GetNamePath, NP, RT.GetNamePath]);
  472. FPattern := NP;
  473. if Assigned(FRoutes.FHandle) then
  474. begin
  475. SgLib.Check;
  476. FRoutes.InternalAdd(Self);
  477. end;
  478. end;
  479. procedure TBrookURLRoute.Validate;
  480. begin
  481. if FPattern.IsEmpty then
  482. raise EBrookURLRoute.CreateFmt(SBrookEmptyRoutePattern, [GetNamePath]);
  483. end;
  484. procedure TBrookURLRoute.DoMatch(ARoute: TBrookURLRoute);
  485. begin
  486. if Assigned(FOnMath) then
  487. FOnMath(ARoute);
  488. end;
  489. procedure TBrookURLRoute.DoRequestMethod(ASender: TObject;
  490. ARoute: TBrookURLRoute; ARequest: TBrookHTTPRequest;
  491. AResponse: TBrookHTTPResponse; var AAllowed: Boolean);
  492. begin
  493. if Assigned(FOnRequestMethod) then
  494. FOnRequestMethod(ASender, ARoute, ARequest, AResponse, AAllowed);
  495. end;
  496. procedure TBrookURLRoute.DoRequest(ASender: TObject; ARoute: TBrookURLRoute;
  497. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  498. begin
  499. if Assigned(FOnRequest) then
  500. FOnRequest(ASender, ARoute, ARequest, AResponse)
  501. else
  502. AResponse.SendEmpty;
  503. end;
  504. procedure TBrookURLRoute.HandleMatch(ARoute: TBrookURLRoute);
  505. var
  506. H: TBrookURLRouteHolder;
  507. begin
  508. H := TBrookURLRouteHolder(ARoute.UserData^);
  509. H.Locker.Unlock;
  510. DoMatch(ARoute);
  511. HandleRequest(H.Sender, TBrookURLRoute(ARoute), H.Request, H.Response);
  512. end;
  513. procedure TBrookURLRoute.HandleRequest(ASender: TObject;
  514. ARoute: TBrookURLRoute; ARequest: TBrookHTTPRequest;
  515. AResponse: TBrookHTTPResponse);
  516. var
  517. A: Boolean;
  518. begin
  519. CheckMethods;
  520. A := IsMethodAllowed(ARequest.Method);
  521. DoRequestMethod(ASender, ARoute, ARequest, AResponse, A);
  522. if A then
  523. DoRequest(ASender, ARoute, ARequest, AResponse)
  524. else
  525. SendMethodNotAllowed(ARequest.Method, AResponse);
  526. end;
  527. function TBrookURLRoute.IsMethodsStored: Boolean;
  528. begin
  529. Result := FMethods <> DefaultReqMethods;
  530. end;
  531. function TBrookURLRoute.IsMethodAllowed(const AMethod: string): Boolean;
  532. begin
  533. Result := (FMethods = []) or (rmUnknown.FromString(AMethod) in FMethods);
  534. end;
  535. procedure TBrookURLRoute.SendMethodNotAllowed(const AMethod: string;
  536. AResponse: TBrookHTTPResponse);
  537. begin
  538. AResponse.SendFmt(SBrookRequestMethodNotAllowed, [AMethod],
  539. BROOK_CT_TEXT_PLAIN, 405);
  540. end;
  541. { TBrookURLRoutesEnumerator }
  542. function TBrookURLRoutesEnumerator.GetCurrent: TBrookURLRoute;
  543. begin
  544. Result := TBrookURLRoute(inherited GetCurrent);
  545. end;
  546. { TBrookURLRoutes }
  547. constructor TBrookURLRoutes.Create(AOwner: TPersistent);
  548. begin
  549. inherited Create(AOwner, GetRouterClass);
  550. SgLib.UnloadEvents.Add(InternalLibUnloadEvent, Self);
  551. end;
  552. destructor TBrookURLRoutes.Destroy;
  553. begin
  554. Unprepare;
  555. SgLib.UnloadEvents.Remove(InternalLibUnloadEvent);
  556. inherited Destroy;
  557. end;
  558. class function TBrookURLRoutes.GetRouterClass: TBrookURLRouteClass;
  559. begin
  560. Result := TBrookURLRoute;
  561. end;
  562. class function TBrookURLRoutes.GetRoutePattern(ARoute: TBrookURLRoute): string;
  563. begin
  564. Result := ARoute.FPattern;
  565. end;
  566. class function TBrookURLRoutes.GetRouteLabel: string;
  567. begin
  568. Result := '/route';
  569. end;
  570. procedure TBrookURLRoutes.InternalLibUnloadEvent(ASender: TObject);
  571. begin
  572. if Assigned(ASender) then
  573. TBrookURLRoutes(ASender).Unprepare;
  574. end;
  575. function TBrookURLRoutes.FindDefault: TBrookURLRoute;
  576. var
  577. R: TBrookURLRoute;
  578. begin
  579. for R in Self do
  580. if R.FDefault then
  581. Exit(R);
  582. Result := nil;
  583. end;
  584. function TBrookURLRoutes.GetHandle: Pointer;
  585. begin
  586. Result := FHandle;
  587. end;
  588. function TBrookURLRoutes.GetEnumerator: TBrookURLRoutesEnumerator;
  589. begin
  590. Result := TBrookURLRoutesEnumerator.Create(Self);
  591. end;
  592. procedure TBrookURLRoutes.InternalAdd(ARoute: TBrookURLRoute);
  593. var
  594. M: TMarshaller;
  595. P: array[0..SG_ERR_SIZE-1] of cchar;
  596. H: Psg_route;
  597. S: string;
  598. R: cint;
  599. begin
  600. P[0] := 0;
  601. R := sg_routes_add2(@FHandle, @H, M.ToCNullableString(GetRoutePattern(ARoute)),
  602. @P[0], SG_ERR_SIZE, ARoute.DoRouteCallback, ARoute);
  603. if R = 0 then
  604. Exit;
  605. if R = EALREADY then
  606. raise EBrookURLRoutes.CreateFmt(SBrookRouteAlreadyExists,
  607. [ARoute.GetNamePath, ARoute.Pattern,
  608. TMarshal.ToString(sg_route_pattern(H))]);
  609. if R = EINVAL then
  610. S := Sagui.StrError(R)
  611. else
  612. S := TMarshal.ToString(@P[0]).TrimRight;
  613. raise EBrookURLRoutes.Create(S);
  614. end;
  615. function TBrookURLRoutes.NewPattern: string;
  616. var
  617. I: Integer;
  618. begin
  619. I := 1;
  620. repeat
  621. Result := Concat(GetRouteLabel, I.ToString);
  622. Inc(I);
  623. until IndexOf(Result) < 0;
  624. end;
  625. procedure TBrookURLRoutes.Prepare;
  626. var
  627. RT: TBrookURLRoute;
  628. begin
  629. if Assigned(FHandle) or (Count = 0) then
  630. Exit;
  631. SgLib.Check;
  632. SgLib.CheckLastError(sg_routes_cleanup(@FHandle));
  633. for RT in Self do
  634. begin
  635. RT.Validate;
  636. InternalAdd(RT);
  637. end;
  638. end;
  639. procedure TBrookURLRoutes.Unprepare;
  640. begin
  641. if not Assigned(FHandle) then
  642. Exit;
  643. SgLib.Check;
  644. SgLib.CheckLastError(sg_routes_cleanup(@FHandle));
  645. end;
  646. function TBrookURLRoutes.Add: TBrookURLRoute;
  647. begin
  648. Result := TBrookURLRoute(inherited Add);
  649. end;
  650. function TBrookURLRoutes.First: TBrookURLRoute;
  651. begin
  652. if Count = 0 then
  653. Exit(nil);
  654. Result := GetItem(0);
  655. end;
  656. function TBrookURLRoutes.Last: TBrookURLRoute;
  657. begin
  658. if Count = 0 then
  659. Exit(nil);
  660. Result := GetItem(Pred(Count));
  661. end;
  662. function TBrookURLRoutes.IndexOf(const APattern: string): Integer;
  663. begin
  664. for Result := 0 to Pred(Count) do
  665. if SameText(GetItem(Result).Pattern, APattern) then
  666. Exit;
  667. Result := -1;
  668. end;
  669. function TBrookURLRoutes.Find(const APattern: string): TBrookURLRoute;
  670. var
  671. RT: TBrookURLRoute;
  672. begin
  673. for RT in Self do
  674. if SameText(RT.Pattern, APattern) then
  675. Exit(RT);
  676. Result := nil;
  677. end;
  678. function TBrookURLRoutes.Remove(const APattern: string): Boolean;
  679. var
  680. M: TMarshaller;
  681. I: Integer;
  682. begin
  683. I := IndexOf(APattern);
  684. Result := I > -1;
  685. if Result then
  686. begin
  687. if Assigned(FHandle) then
  688. SgLib.CheckLastError(sg_routes_rm(@FHandle, M.ToCString(APattern)));
  689. inherited Delete(I);
  690. end;
  691. end;
  692. function TBrookURLRoutes.GetItem(AIndex: Integer): TBrookURLRoute;
  693. begin
  694. Result := TBrookURLRoute(inherited GetItem(AIndex));
  695. end;
  696. procedure TBrookURLRoutes.SetItem(AIndex: Integer; AValue: TBrookURLRoute);
  697. begin
  698. inherited SetItem(AIndex, AValue);
  699. end;
  700. procedure TBrookURLRoutes.Clear;
  701. begin
  702. inherited Clear;
  703. Unprepare;
  704. end;
  705. { TBrookURLRouter }
  706. constructor TBrookURLRouter.Create(AOwner: TComponent);
  707. begin
  708. inherited Create(AOwner);
  709. FLocker := CreateLocker;
  710. FRoutes := CreateRoutes;
  711. SgLib.UnloadEvents.Add(InternalLibUnloadEvent, Self);
  712. end;
  713. destructor TBrookURLRouter.Destroy;
  714. begin
  715. SetActive(False);
  716. FRoutes.Free;
  717. FLocker.Free;
  718. SgLib.UnloadEvents.Remove(InternalLibUnloadEvent);
  719. inherited Destroy;
  720. end;
  721. function TBrookURLRouter.CreateLocker: TBrookLocker;
  722. begin
  723. Result := TBrookLocker.Create;
  724. end;
  725. function TBrookURLRouter.CreateRoutes: TBrookURLRoutes;
  726. begin
  727. Result := TBrookURLRoutes.Create(Self);
  728. end;
  729. function TBrookURLRouter.GetEnumerator: TBrookURLRoutesEnumerator;
  730. begin
  731. Result := TBrookURLRoutesEnumerator.Create(FRoutes);
  732. end;
  733. procedure TBrookURLRouter.InternalLibUnloadEvent(ASender: TObject);
  734. begin
  735. if Assigned(ASender) then
  736. TBrookURLRouter(ASender).Close;
  737. end;
  738. procedure TBrookURLRouter.CheckItems;
  739. begin
  740. if FRoutes.Count = 0 then
  741. raise EBrookURLRoutes.Create(SBrookNoRoutesDefined);
  742. end;
  743. procedure TBrookURLRouter.CheckActive;
  744. begin
  745. if (not (csLoading in ComponentState)) and (not Active) then
  746. raise EInvalidOpException.Create(SBrookInactiveRouter);
  747. end;
  748. procedure TBrookURLRouter.Loaded;
  749. begin
  750. inherited Loaded;
  751. try
  752. if FStreamedActive then
  753. SetActive(True);
  754. except
  755. if csDesigning in ComponentState then
  756. begin
  757. if Assigned(ApplicationHandleException) then
  758. ApplicationHandleException(ExceptObject)
  759. else
  760. ShowException(ExceptObject, ExceptAddr);
  761. end
  762. else
  763. raise;
  764. end;
  765. end;
  766. function TBrookURLRouter.GetHandle: Pointer;
  767. begin
  768. Result := FHandle;
  769. end;
  770. function TBrookURLRouter.Add: TBrookURLRoute;
  771. begin
  772. Result := FRoutes.Add;
  773. end;
  774. procedure TBrookURLRouter.Remove(const APattern: string);
  775. begin
  776. FRoutes.Remove(APattern);
  777. end;
  778. procedure TBrookURLRouter.Clear;
  779. begin
  780. FRoutes.Clear;
  781. end;
  782. function TBrookURLRouter.GetItem(AIndex: Integer): TBrookURLRoute;
  783. begin
  784. Result := FRoutes[AIndex];
  785. end;
  786. procedure TBrookURLRouter.SetItem(AIndex: Integer; AValue: TBrookURLRoute);
  787. begin
  788. FRoutes[AIndex] := AValue;
  789. end;
  790. procedure TBrookURLRouter.SetRoutes(AValue: TBrookURLRoutes);
  791. begin
  792. if AValue = FRoutes then
  793. Exit;
  794. if Assigned(AValue) then
  795. FRoutes.Assign(AValue)
  796. else
  797. FRoutes.Clear;
  798. end;
  799. function TBrookURLRouter.IsActiveStored: Boolean;
  800. begin
  801. Result := FActive;
  802. end;
  803. procedure TBrookURLRouter.SetActive(AValue: Boolean);
  804. begin
  805. if AValue = FActive then
  806. Exit;
  807. if csDesigning in ComponentState then
  808. begin
  809. if not (csLoading in ComponentState) then
  810. begin
  811. SgLib.Check;
  812. if AValue then
  813. CheckItems;
  814. end;
  815. FActive := AValue;
  816. end
  817. else
  818. if AValue then
  819. begin
  820. if csReading in ComponentState then
  821. FStreamedActive := True
  822. else
  823. DoOpen;
  824. end
  825. else
  826. DoClose;
  827. end;
  828. procedure TBrookURLRouter.DoOpen;
  829. begin
  830. if Assigned(FHandle) then
  831. Exit;
  832. FRoutes.Prepare;
  833. SgLib.Check;
  834. FHandle := sg_router_new(FRoutes.Handle);
  835. FActive := Assigned(FHandle);
  836. if Assigned(FOnActivate) then
  837. FOnActivate(Self);
  838. end;
  839. procedure TBrookURLRouter.DoClose;
  840. begin
  841. if not Assigned(FHandle) then
  842. Exit;
  843. SgLib.Check;
  844. sg_router_free(FHandle);
  845. FHandle := nil;
  846. FActive := False;
  847. if Assigned(FOnDeactivate) then
  848. FOnDeactivate(Self);
  849. end;
  850. procedure TBrookURLRouter.Open;
  851. begin
  852. SetActive(True);
  853. end;
  854. procedure TBrookURLRouter.Close;
  855. begin
  856. SetActive(False);
  857. end;
  858. procedure TBrookURLRouter.DoRoute(ASender: TObject; const ARoute: string;
  859. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  860. begin
  861. if Assigned(FOnRoute) then
  862. FOnRoute(ASender, ARoute, ARequest, AResponse);
  863. end;
  864. procedure TBrookURLRouter.DoNotFound(ASender: TObject; const ARoute: string;
  865. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  866. begin
  867. if Assigned(FOnNotFound) then
  868. FOnNotFound(ASender, ARoute, ARequest, AResponse)
  869. else
  870. AResponse.SendFmt(SBrookRouteNotFound, [ARoute], BROOK_CT_TEXT_PLAIN, 404);
  871. end;
  872. function TBrookURLRouter.DispatchRoute(const APath: string;
  873. AUserData: Pointer): Boolean;
  874. var
  875. M: TMarshaller;
  876. R: cint;
  877. begin
  878. CheckItems;
  879. CheckActive;
  880. SgLib.Check;
  881. R := sg_router_dispatch(FHandle,
  882. M.ToCNullableString(Brook.FixPath(APath)), AUserData);
  883. Result := R = 0;
  884. if (not Result) and (R <> ENOENT) then
  885. SgLib.CheckLastError(R);
  886. end;
  887. procedure TBrookURLRouter.Route(ASender: TObject; const APath: string;
  888. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  889. var
  890. H: TBrookURLRouteHolder;
  891. R: TBrookURLRoute;
  892. B: Boolean;
  893. begin
  894. FLocker.Lock;
  895. H.Locker := FLocker;
  896. H.Request := ARequest;
  897. H.Response := AResponse;
  898. H.Sender := ASender;
  899. try
  900. B := DispatchRoute(APath, @H);
  901. except
  902. FLocker.Unlock;
  903. raise;
  904. end;
  905. if B then
  906. begin
  907. DoRoute(ASender, APath, ARequest, AResponse);
  908. Exit;
  909. end;
  910. if APath = '/' then
  911. begin
  912. R := FRoutes.FindDefault;
  913. if Assigned(R) then
  914. begin
  915. R.HandleRequest(ASender, R, ARequest, AResponse);
  916. FLocker.Unlock;
  917. Exit;
  918. end;
  919. end;
  920. DoNotFound(ASender, APath, ARequest, AResponse);
  921. FLocker.Unlock;
  922. end;
  923. procedure TBrookURLRouter.Route(ASender: TObject;
  924. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  925. begin
  926. if not Assigned(ARequest) then
  927. raise EArgumentNilException.CreateFmt(SParamIsNil, ['ARequest']);
  928. Route(ASender, ARequest.Path, ARequest, AResponse);
  929. end;
  930. end.