BrookURLRouter.pas 29 KB

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