BrookURLRouter.pas 29 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015
  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. FRoutes: TBrookURLRoutes;
  233. FHandle: Psg_router;
  234. FActive: Boolean;
  235. FStreamedActive: Boolean;
  236. FOnNotFound: TBrookURLRouterRouteEvent;
  237. FOnRoute: TBrookURLRouterRouteEvent;
  238. FOnActivate: TNotifyEvent;
  239. FOnDeactivate: TNotifyEvent;
  240. function GetItem(AIndex: Integer): TBrookURLRoute;
  241. function IsActiveStored: Boolean;
  242. procedure SetActive(AValue: Boolean);
  243. procedure SetItem(AIndex: Integer; AValue: TBrookURLRoute);
  244. procedure SetRoutes(AValue: TBrookURLRoutes);
  245. procedure InternalLibUnloadEvent(ASender: TObject);
  246. protected
  247. function CreateRoutes: TBrookURLRoutes; virtual;
  248. procedure Loaded; override;
  249. function GetHandle: Pointer; override;
  250. procedure DoRoute(ASender: TObject; const ARoute: string;
  251. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse); virtual;
  252. procedure DoNotFound(ASender: TObject; const ARoute: string;
  253. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse); virtual;
  254. procedure DoOpen; virtual;
  255. procedure DoClose; virtual;
  256. procedure CheckItems; {$IFNDEF DEBUG}inline;{$ENDIF}
  257. procedure CheckActive; {$IFNDEF DEBUG}inline;{$ENDIF}
  258. public
  259. { Creates an instance of @code(TBrookURLRouter).
  260. @param(AOwner[in] Owner component.) }
  261. constructor Create(AOwner: TComponent); override;
  262. { Destroys an instance of @code(TBrookURLRouter). }
  263. destructor Destroy; override;
  264. { Creates an enumerator to iterate the routes through @code(for..in). }
  265. function GetEnumerator: TBrookURLRoutesEnumerator;
  266. { Adds a new item to the routes list.
  267. @returns(Route item.) }
  268. function Add: TBrookURLRoute; {$IFNDEF DEBUG}inline;{$ENDIF}
  269. { Removes an item from the routes list by its pattern.
  270. @param(APattern[in] Route name.) }
  271. procedure Remove(const APattern: string); {$IFNDEF DEBUG}inline;{$ENDIF}
  272. { Clears the routes list. }
  273. procedure Clear; {$IFNDEF DEBUG}inline;{$ENDIF}
  274. { Enabled the router component. }
  275. procedure Open;
  276. { Disables the router component. }
  277. procedure Close;
  278. { Finds a route and dispatches it to the client.
  279. @param(APath[in] Route path.)
  280. @param(AUserData[in] User-defined data.) }
  281. function DispatchRoute(const APath: string;
  282. AUserData: Pointer): Boolean; virtual;
  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. Request: TBrookHTTPRequest;
  320. Response: TBrookHTTPResponse;
  321. Sender: TObject;
  322. end;
  323. { TBrookURLRoute }
  324. constructor TBrookURLRoute.Create(ACollection: TCollection);
  325. begin
  326. inherited Create(ACollection);
  327. FVariables := TBrookStringMap.Create(@Fvars);
  328. if Assigned(ACollection) and (ACollection is TBrookURLRoutes) then
  329. begin
  330. FRoutes := ACollection as TBrookURLRoutes;
  331. FPattern := FRoutes.NewPattern;
  332. end
  333. else
  334. FPattern := '/';
  335. FMethods := DefaultReqMethods;
  336. end;
  337. destructor TBrookURLRoute.Destroy;
  338. begin
  339. FVariables.ClearOnDestroy := False;
  340. FVariables.Free;
  341. inherited Destroy;
  342. end;
  343. class procedure TBrookURLRoute.DoRouteCallback(Acls: Pcvoid; Aroute: Psg_route);
  344. var
  345. VRoute: TBrookURLRoute;
  346. begin
  347. VRoute := Acls;
  348. VRoute.FHandle := Aroute;
  349. VRoute.HandleMatch(VRoute);
  350. end;
  351. {$IFDEF FPC}
  352. {$PUSH}{$WARN 5024 OFF}
  353. {$ENDIF}
  354. class function TBrookURLRoute.DoSegmentsIterCallback(Acls: Pcvoid;
  355. Aindex: cuint; //FI:O804
  356. const Asegment: Pcchar): cint;
  357. var
  358. VSegments: ^TArray<string>;
  359. begin
  360. VSegments := Acls;
  361. VSegments^ := Concat(VSegments^, [TMarshal.ToString(Asegment)]);
  362. Result := 0;
  363. end;
  364. {$IFDEF FPC}
  365. {$POP}
  366. {$ENDIF}
  367. class function TBrookURLRoute.DoVarsIterCallback(Acls: Pcvoid;
  368. const Aname: Pcchar; const Aval: Pcchar): cint;
  369. begin
  370. TBrookStringMap(Acls).Add(TMarshal.ToString(Aname), TMarshal.ToString(Aval));
  371. Result := 0;
  372. end;
  373. procedure TBrookURLRoute.CheckMethods;
  374. begin
  375. if FMethods = [rmUnknown] then
  376. raise EBrookURLRoute.Create(SBrookRequestNoMethodDefined);
  377. end;
  378. function TBrookURLRoute.GetHandle: Pointer;
  379. begin
  380. Result := FHandle;
  381. end;
  382. function TBrookURLRoute.GetPCRE2Handle: Pointer;
  383. begin
  384. if not Assigned(FHandle) then
  385. Exit(nil);
  386. SgLib.Check;
  387. Result := sg_route_handle(FHandle);
  388. end;
  389. function TBrookURLRoute.GetSegments: TArray<string>;
  390. begin
  391. Result := nil;
  392. if not Assigned(FHandle) then
  393. Exit(nil);
  394. SgLib.Check;
  395. SgLib.CheckLastError(sg_route_segments_iter(FHandle, DoSegmentsIterCallback,
  396. @Result));
  397. end;
  398. function TBrookURLRoute.GetVariables: TBrookStringMap;
  399. begin
  400. Result := FVariables;
  401. if not Assigned(FHandle) then
  402. Exit;
  403. FVariables.Clear;
  404. SgLib.Check;
  405. SgLib.CheckLastError(sg_route_vars_iter(FHandle, DoVarsIterCallback,
  406. FVariables));
  407. end;
  408. function TBrookURLRoute.GetRawPattern: string;
  409. begin
  410. if not Assigned(FHandle) then
  411. begin
  412. if FPattern.IsEmpty then
  413. Exit('');
  414. Exit(Concat('^', FPattern, '$'));
  415. end;
  416. SgLib.Check;
  417. Result := TMarshal.ToString(sg_route_rawpattern(FHandle));
  418. end;
  419. function TBrookURLRoute.GetPattern: string;
  420. var
  421. P: Pcchar;
  422. begin
  423. if not Assigned(FHandle) then
  424. Exit(FPattern);
  425. SgLib.Check;
  426. P := sg_route_pattern(FHandle);
  427. try
  428. Result := TMarshal.ToString(P);
  429. finally
  430. sg_free(P);
  431. end;
  432. end;
  433. function TBrookURLRoute.GetPath: string;
  434. begin
  435. if not Assigned(FHandle) then
  436. Exit('');
  437. SgLib.Check;
  438. Result := TMarshal.ToString(sg_route_path(FHandle));
  439. end;
  440. function TBrookURLRoute.GetUserData: Pointer;
  441. begin
  442. if not Assigned(FHandle) then
  443. Exit(nil);
  444. SgLib.Check;
  445. Result := sg_route_user_data(FHandle);
  446. end;
  447. function TBrookURLRoute.IsDefaultStored: Boolean;
  448. begin
  449. Result := FDefault;
  450. end;
  451. procedure TBrookURLRoute.SetDefault(AValue: Boolean);
  452. begin
  453. if FDefault = AValue then
  454. Exit;
  455. if AValue and Assigned(FRoutes) and Assigned(FRoutes.FindDefault()) then
  456. raise EBrookURLRoute.Create(SBrookDefaultRouteAlreadyExists);
  457. FDefault := AValue;
  458. end;
  459. procedure TBrookURLRoute.SetPattern(const AValue: string);
  460. var
  461. RT: TBrookURLRoute;
  462. NP: string;
  463. begin
  464. if (AValue = FPattern) or (not Assigned(FRoutes)) then
  465. Exit;
  466. NP := Brook.FixPath(AValue);
  467. RT := FRoutes.Find(NP);
  468. if Assigned(RT) and (RT <> Self) then
  469. raise EBrookURLRoute.CreateFmt(SBrookRouteAlreadyExists,
  470. [GetNamePath, NP, RT.GetNamePath]);
  471. FPattern := NP;
  472. if Assigned(FRoutes.FHandle) then
  473. begin
  474. SgLib.Check;
  475. FRoutes.InternalAdd(Self);
  476. end;
  477. end;
  478. procedure TBrookURLRoute.Validate;
  479. begin
  480. if FPattern.IsEmpty then
  481. raise EBrookURLRoute.CreateFmt(SBrookEmptyRoutePattern, [GetNamePath]);
  482. end;
  483. procedure TBrookURLRoute.DoMatch(ARoute: TBrookURLRoute);
  484. begin
  485. if Assigned(FOnMath) then
  486. FOnMath(ARoute);
  487. end;
  488. procedure TBrookURLRoute.DoRequestMethod(ASender: TObject;
  489. ARoute: TBrookURLRoute; ARequest: TBrookHTTPRequest;
  490. AResponse: TBrookHTTPResponse; var AAllowed: Boolean);
  491. begin
  492. if Assigned(FOnRequestMethod) then
  493. FOnRequestMethod(ASender, ARoute, ARequest, AResponse, AAllowed);
  494. end;
  495. procedure TBrookURLRoute.DoRequest(ASender: TObject; ARoute: TBrookURLRoute;
  496. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  497. begin
  498. if Assigned(FOnRequest) then
  499. FOnRequest(ASender, ARoute, ARequest, AResponse)
  500. else
  501. AResponse.SendEmpty;
  502. end;
  503. procedure TBrookURLRoute.HandleMatch(ARoute: TBrookURLRoute);
  504. var
  505. H: TBrookURLRouteHolder;
  506. begin
  507. DoMatch(ARoute);
  508. H := TBrookURLRouteHolder(ARoute.UserData^);
  509. HandleRequest(H.Sender, TBrookURLRoute(ARoute), H.Request, H.Response);
  510. end;
  511. procedure TBrookURLRoute.HandleRequest(ASender: TObject;
  512. ARoute: TBrookURLRoute; ARequest: TBrookHTTPRequest;
  513. AResponse: TBrookHTTPResponse);
  514. var
  515. A: Boolean;
  516. begin
  517. CheckMethods;
  518. A := IsMethodAllowed(ARequest.Method);
  519. DoRequestMethod(ASender, ARoute, ARequest, AResponse, A);
  520. if A then
  521. DoRequest(ASender, ARoute, ARequest, AResponse)
  522. else
  523. SendMethodNotAllowed(ARequest.Method, AResponse);
  524. end;
  525. function TBrookURLRoute.IsMethodsStored: Boolean;
  526. begin
  527. Result := FMethods <> DefaultReqMethods;
  528. end;
  529. function TBrookURLRoute.IsMethodAllowed(const AMethod: string): Boolean;
  530. begin
  531. Result := (FMethods = []) or (rmUnknown.FromString(AMethod) in FMethods);
  532. end;
  533. procedure TBrookURLRoute.SendMethodNotAllowed(const AMethod: string;
  534. AResponse: TBrookHTTPResponse);
  535. begin
  536. AResponse.SendFmt(SBrookRequestMethodNotAllowed, [AMethod],
  537. BROOK_CT_TEXT_PLAIN, 405);
  538. end;
  539. { TBrookURLRoutesEnumerator }
  540. function TBrookURLRoutesEnumerator.GetCurrent: TBrookURLRoute;
  541. begin
  542. Result := TBrookURLRoute(inherited GetCurrent);
  543. end;
  544. { TBrookURLRoutes }
  545. constructor TBrookURLRoutes.Create(AOwner: TPersistent);
  546. begin
  547. inherited Create(AOwner, GetRouterClass);
  548. SgLib.UnloadEvents.Add(InternalLibUnloadEvent, Self);
  549. end;
  550. destructor TBrookURLRoutes.Destroy;
  551. begin
  552. Unprepare;
  553. SgLib.UnloadEvents.Remove(InternalLibUnloadEvent);
  554. inherited Destroy;
  555. end;
  556. class function TBrookURLRoutes.GetRouterClass: TBrookURLRouteClass;
  557. begin
  558. Result := TBrookURLRoute;
  559. end;
  560. class function TBrookURLRoutes.GetRoutePattern(ARoute: TBrookURLRoute): string;
  561. begin
  562. Result := ARoute.FPattern;
  563. end;
  564. class function TBrookURLRoutes.GetRouteLabel: string;
  565. begin
  566. Result := '/route';
  567. end;
  568. procedure TBrookURLRoutes.InternalLibUnloadEvent(ASender: TObject);
  569. begin
  570. if Assigned(ASender) then
  571. TBrookURLRoutes(ASender).Unprepare;
  572. end;
  573. function TBrookURLRoutes.FindDefault: TBrookURLRoute;
  574. var
  575. R: TBrookURLRoute;
  576. begin
  577. for R in Self do
  578. if R.FDefault then
  579. Exit(R);
  580. Result := nil;
  581. end;
  582. function TBrookURLRoutes.GetHandle: Pointer;
  583. begin
  584. Result := FHandle;
  585. end;
  586. function TBrookURLRoutes.GetEnumerator: TBrookURLRoutesEnumerator;
  587. begin
  588. Result := TBrookURLRoutesEnumerator.Create(Self);
  589. end;
  590. procedure TBrookURLRoutes.InternalAdd(ARoute: TBrookURLRoute);
  591. var
  592. M: TMarshaller;
  593. P: array[0..SG_ERR_SIZE-1] of cchar;
  594. H: Psg_route;
  595. S: string;
  596. R: cint;
  597. begin
  598. P[0] := 0;
  599. R := sg_routes_add2(@FHandle, @H, M.ToCNullableString(GetRoutePattern(ARoute)),
  600. @P[0], SG_ERR_SIZE, ARoute.DoRouteCallback, ARoute);
  601. if R = 0 then
  602. Exit;
  603. if R = EALREADY then
  604. raise EBrookURLRoutes.CreateFmt(SBrookRouteAlreadyExists,
  605. [ARoute.GetNamePath, ARoute.Pattern]);
  606. if R = EINVAL then
  607. S := Sagui.StrError(R)
  608. else
  609. S := TMarshal.ToString(@P[0]).TrimRight;
  610. raise EBrookURLRoutes.Create(S);
  611. end;
  612. function TBrookURLRoutes.NewPattern: string;
  613. var
  614. I: Integer;
  615. begin
  616. I := 1;
  617. repeat
  618. Result := Concat(GetRouteLabel, I.ToString);
  619. Inc(I);
  620. until IndexOf(Result) < 0;
  621. end;
  622. procedure TBrookURLRoutes.Prepare;
  623. var
  624. RT: TBrookURLRoute;
  625. begin
  626. if Assigned(FHandle) or (Count = 0) then
  627. Exit;
  628. SgLib.Check;
  629. SgLib.CheckLastError(sg_routes_cleanup(@FHandle));
  630. for RT in Self do
  631. begin
  632. RT.Validate;
  633. InternalAdd(RT);
  634. end;
  635. end;
  636. procedure TBrookURLRoutes.Unprepare;
  637. begin
  638. if not Assigned(FHandle) then
  639. Exit;
  640. SgLib.Check;
  641. SgLib.CheckLastError(sg_routes_cleanup(@FHandle));
  642. end;
  643. function TBrookURLRoutes.Add: TBrookURLRoute;
  644. begin
  645. Result := TBrookURLRoute(inherited Add);
  646. end;
  647. function TBrookURLRoutes.First: TBrookURLRoute;
  648. begin
  649. if Count = 0 then
  650. Exit(nil);
  651. Result := GetItem(0);
  652. end;
  653. function TBrookURLRoutes.Last: TBrookURLRoute;
  654. begin
  655. if Count = 0 then
  656. Exit(nil);
  657. Result := GetItem(Pred(Count));
  658. end;
  659. function TBrookURLRoutes.IndexOf(const APattern: string): Integer;
  660. begin
  661. for Result := 0 to Pred(Count) do
  662. if SameText(GetItem(Result).Pattern, APattern) then
  663. Exit;
  664. Result := -1;
  665. end;
  666. function TBrookURLRoutes.Find(const APattern: string): TBrookURLRoute;
  667. var
  668. RT: TBrookURLRoute;
  669. begin
  670. for RT in Self do
  671. if SameText(RT.Pattern, APattern) then
  672. Exit(RT);
  673. Result := nil;
  674. end;
  675. function TBrookURLRoutes.Remove(const APattern: string): Boolean;
  676. var
  677. M: TMarshaller;
  678. I: Integer;
  679. begin
  680. I := IndexOf(APattern);
  681. Result := I > -1;
  682. if Result then
  683. begin
  684. if Assigned(FHandle) then
  685. SgLib.CheckLastError(sg_routes_rm(@FHandle, M.ToCString(APattern)));
  686. inherited Delete(I);
  687. end;
  688. end;
  689. function TBrookURLRoutes.GetItem(AIndex: Integer): TBrookURLRoute;
  690. begin
  691. Result := TBrookURLRoute(inherited GetItem(AIndex));
  692. end;
  693. procedure TBrookURLRoutes.SetItem(AIndex: Integer; AValue: TBrookURLRoute);
  694. begin
  695. inherited SetItem(AIndex, AValue);
  696. end;
  697. procedure TBrookURLRoutes.Clear;
  698. begin
  699. inherited Clear;
  700. Unprepare;
  701. end;
  702. { TBrookURLRouter }
  703. constructor TBrookURLRouter.Create(AOwner: TComponent);
  704. begin
  705. inherited Create(AOwner);
  706. FRoutes := CreateRoutes;
  707. SgLib.UnloadEvents.Add(InternalLibUnloadEvent, Self);
  708. end;
  709. destructor TBrookURLRouter.Destroy;
  710. begin
  711. SetActive(False);
  712. FRoutes.Free;
  713. SgLib.UnloadEvents.Remove(InternalLibUnloadEvent);
  714. inherited Destroy;
  715. end;
  716. function TBrookURLRouter.CreateRoutes: TBrookURLRoutes;
  717. begin
  718. Result := TBrookURLRoutes.Create(Self);
  719. end;
  720. function TBrookURLRouter.GetEnumerator: TBrookURLRoutesEnumerator;
  721. begin
  722. Result := TBrookURLRoutesEnumerator.Create(FRoutes);
  723. end;
  724. procedure TBrookURLRouter.InternalLibUnloadEvent(ASender: TObject);
  725. begin
  726. if Assigned(ASender) then
  727. TBrookURLRouter(ASender).Close;
  728. end;
  729. procedure TBrookURLRouter.CheckItems;
  730. begin
  731. if FRoutes.Count = 0 then
  732. raise EBrookURLRoutes.Create(SBrookNoRoutesDefined);
  733. end;
  734. procedure TBrookURLRouter.CheckActive;
  735. begin
  736. if (not (csLoading in ComponentState)) and (not Active) then
  737. raise EInvalidOpException.Create(SBrookInactiveRouter);
  738. end;
  739. procedure TBrookURLRouter.Loaded;
  740. begin
  741. inherited Loaded;
  742. try
  743. if FStreamedActive then
  744. SetActive(True);
  745. except
  746. if csDesigning in ComponentState then
  747. begin
  748. if Assigned(ApplicationHandleException) then
  749. ApplicationHandleException(ExceptObject)
  750. else
  751. ShowException(ExceptObject, ExceptAddr);
  752. end
  753. else
  754. raise;
  755. end;
  756. end;
  757. function TBrookURLRouter.GetHandle: Pointer;
  758. begin
  759. Result := FHandle;
  760. end;
  761. function TBrookURLRouter.Add: TBrookURLRoute;
  762. begin
  763. Result := FRoutes.Add;
  764. end;
  765. procedure TBrookURLRouter.Remove(const APattern: string);
  766. begin
  767. FRoutes.Remove(APattern);
  768. end;
  769. procedure TBrookURLRouter.Clear;
  770. begin
  771. FRoutes.Clear;
  772. end;
  773. function TBrookURLRouter.GetItem(AIndex: Integer): TBrookURLRoute;
  774. begin
  775. Result := FRoutes[AIndex];
  776. end;
  777. procedure TBrookURLRouter.SetItem(AIndex: Integer; AValue: TBrookURLRoute);
  778. begin
  779. FRoutes[AIndex] := AValue;
  780. end;
  781. procedure TBrookURLRouter.SetRoutes(AValue: TBrookURLRoutes);
  782. begin
  783. if AValue = FRoutes then
  784. Exit;
  785. if Assigned(AValue) then
  786. FRoutes.Assign(AValue)
  787. else
  788. FRoutes.Clear;
  789. end;
  790. function TBrookURLRouter.IsActiveStored: Boolean;
  791. begin
  792. Result := FActive;
  793. end;
  794. procedure TBrookURLRouter.SetActive(AValue: Boolean);
  795. begin
  796. if AValue = FActive then
  797. Exit;
  798. if csDesigning in ComponentState then
  799. begin
  800. if not (csLoading in ComponentState) then
  801. begin
  802. SgLib.Check;
  803. if AValue then
  804. CheckItems;
  805. end;
  806. FActive := AValue;
  807. end
  808. else
  809. if AValue then
  810. begin
  811. if csReading in ComponentState then
  812. FStreamedActive := True
  813. else
  814. DoOpen;
  815. end
  816. else
  817. DoClose;
  818. end;
  819. procedure TBrookURLRouter.DoOpen;
  820. begin
  821. if Assigned(FHandle) then
  822. Exit;
  823. FRoutes.Prepare;
  824. SgLib.Check;
  825. FHandle := sg_router_new(FRoutes.Handle);
  826. FActive := Assigned(FHandle);
  827. if Assigned(FOnActivate) then
  828. FOnActivate(Self);
  829. end;
  830. procedure TBrookURLRouter.DoClose;
  831. begin
  832. if not Assigned(FHandle) then
  833. Exit;
  834. SgLib.Check;
  835. sg_router_free(FHandle);
  836. FHandle := nil;
  837. FActive := False;
  838. if Assigned(FOnDeactivate) then
  839. FOnDeactivate(Self);
  840. end;
  841. procedure TBrookURLRouter.Open;
  842. begin
  843. SetActive(True);
  844. end;
  845. procedure TBrookURLRouter.Close;
  846. begin
  847. SetActive(False);
  848. end;
  849. procedure TBrookURLRouter.DoRoute(ASender: TObject; const ARoute: string;
  850. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  851. begin
  852. if Assigned(FOnRoute) then
  853. FOnRoute(ASender, ARoute, ARequest, AResponse);
  854. end;
  855. procedure TBrookURLRouter.DoNotFound(ASender: TObject; const ARoute: string;
  856. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  857. begin
  858. if Assigned(FOnNotFound) then
  859. FOnNotFound(ASender, ARoute, ARequest, AResponse)
  860. else
  861. AResponse.SendFmt(SBrookRouteNotFound, [ARoute], BROOK_CT_TEXT_PLAIN, 404);
  862. end;
  863. function TBrookURLRouter.DispatchRoute(const APath: string;
  864. AUserData: Pointer): Boolean;
  865. var
  866. M: TMarshaller;
  867. R: cint;
  868. begin
  869. CheckItems;
  870. CheckActive;
  871. SgLib.Check;
  872. R := sg_router_dispatch(FHandle,
  873. M.ToCNullableString(Brook.FixPath(APath)), AUserData);
  874. Result := R = 0;
  875. if (not Result) and (R <> ENOENT) then
  876. SgLib.CheckLastError(R);
  877. end;
  878. procedure TBrookURLRouter.Route(ASender: TObject; const APath: string;
  879. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  880. var
  881. H: TBrookURLRouteHolder;
  882. R: TBrookURLRoute;
  883. begin
  884. H.Request := ARequest;
  885. H.Response := AResponse;
  886. H.Sender := ASender;
  887. if DispatchRoute(APath, @H) then
  888. begin
  889. DoRoute(ASender, APath, ARequest, AResponse);
  890. Exit;
  891. end;
  892. if APath = '/' then
  893. begin
  894. R := FRoutes.FindDefault;
  895. if Assigned(R) then
  896. begin
  897. R.HandleRequest(ASender, R, ARequest, AResponse);
  898. Exit;
  899. end;
  900. end;
  901. DoNotFound(ASender, APath, ARequest, AResponse);
  902. end;
  903. procedure TBrookURLRouter.Route(ASender: TObject;
  904. ARequest: TBrookHTTPRequest; AResponse: TBrookHTTPResponse);
  905. begin
  906. if not Assigned(ARequest) then
  907. raise EArgumentNilException.CreateFmt(SParamIsNil, ['ARequest']);
  908. Route(ASender, ARequest.Path, ARequest, AResponse);
  909. end;
  910. end.