restconnection.pas 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by Michael Van Canneyt, member of the
  4. Free Pascal development team
  5. Simple REST connection component for use with Datasets.
  6. See the file COPYING.FPC, included in this distribution,
  7. for details about the copyright.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  11. **********************************************************************}
  12. unit RestConnection;
  13. {$mode objfpc}
  14. interface
  15. uses
  16. Classes, SysUtils, Web, DB;
  17. Type
  18. { TRESTConnection }
  19. TRestGetURLEvent = Procedure (Sender : TComponent; aRequest : TDataRequest; Var aURL : String) of Object;
  20. TRestUpdateURLEvent = Procedure (Sender : TComponent; aRequest : TRecordUpdateDescriptor; Var aURL : String) of Object;
  21. TRESTConnection = Class(TComponent)
  22. private
  23. FBaseURL: String;
  24. FDataProxy : TDataProxy;
  25. FOnGetURL: TRestGetURLEvent;
  26. FOnUpdateURL: TRestUpdateURLEvent;
  27. FPageParam: String;
  28. function GetDataProxy: TDataProxy;
  29. Protected
  30. Procedure SetupRequest(aXHR : TJSXMLHttpRequest); virtual;
  31. Function GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor) : String; virtual;
  32. Function GetReadBaseURL(aRequest: TDataRequest) : String; virtual;
  33. Function GetPageURL(aRequest : TDataRequest) : String; virtual;
  34. Function GetRecordUpdateURL(aRequest : TRecordUpdateDescriptor) : String;
  35. Public
  36. Function DoGetDataProxy : TDataProxy; virtual;
  37. Public
  38. Property DataProxy : TDataProxy Read GetDataProxy;
  39. Property BaseURL : String Read FBaseURL Write FBaseURL;
  40. Property PageParam : String Read FPageParam Write FPageParam;
  41. Property OnGetURL : TRestGetURLEvent Read FOnGetURL Write FOnGetURL;
  42. Property OnUpdateURL : TRestUpdateURLEvent Read FOnUpdateURL Write FOnUpdateURL;
  43. end;
  44. { TRESTDataProxy }
  45. TRESTDataProxy = class(TDataProxy)
  46. private
  47. FConnection: TRESTConnection;
  48. protected
  49. Procedure CheckBatchComplete(aBatch : TRecordUpdateBatch); virtual;
  50. Public
  51. Function GetUpdateDescriptorClass : TRecordUpdateDescriptorClass; override;
  52. Function ProcessUpdateBatch(aBatch : TRecordUpdateBatch): Boolean; override;
  53. Function DoGetData(aRequest: TDataRequest): Boolean; override;
  54. Function GetDataRequest(aOptions: TLoadOptions; aAfterRequest: TDataRequestEvent; aAfterLoad: TDatasetLoadEvent) : TDataRequest; override;
  55. Constructor Create(AOwner: TComponent); override;
  56. Property Connection : TRESTConnection Read FConnection;
  57. end;
  58. { TRESTDataRequest }
  59. TRESTDataRequest = Class(TDataRequest)
  60. Private
  61. FXHR : TJSXMLHttpRequest;
  62. protected
  63. function onLoad(Event{%H-}: TEventListenerEvent): boolean; virtual;
  64. function TransformResult: JSValue; virtual;
  65. end;
  66. { TRESTUpdateRequest }
  67. TRESTUpdateRequest = Class(TRecordUpdateDescriptor)
  68. Private
  69. FXHR : TJSXMLHttpRequest;
  70. FBatch : TRecordUpdateBatch;
  71. protected
  72. function onLoad(Event{%H-}: TEventListenerEvent): boolean; virtual;
  73. end;
  74. implementation
  75. uses js;
  76. { TRESTUpdateRequest }
  77. function TRESTUpdateRequest.onLoad(Event: TEventListenerEvent): boolean;
  78. begin
  79. if (FXHR.Status div 100)=2 then
  80. begin
  81. Resolve(FXHR.response);
  82. Result:=True;
  83. end
  84. else
  85. ResolveFailed(FXHR.StatusText);
  86. (Proxy as TRestDataProxy).CheckBatchComplete(FBatch);
  87. end;
  88. { TRESTDataRequest }
  89. function TRESTDataRequest.TransformResult : JSValue;
  90. begin
  91. Result:=FXHR.responseText;
  92. end;
  93. function TRESTDataRequest.onLoad(Event: TEventListenerEvent): boolean;
  94. begin
  95. if (FXHR.Status=200) then
  96. begin
  97. Data:=TransformResult;
  98. Success:=rrOK;
  99. end
  100. else
  101. begin
  102. Data:=Nil;
  103. if (loAtEOF in LoadOptions) and (FXHR.Status=404) then
  104. Success:=rrEOF
  105. else
  106. begin
  107. Success:=rrFail;
  108. ErrorMsg:=FXHR.StatusText;
  109. end;
  110. end;
  111. DoAfterRequest;
  112. Result:=True;
  113. end;
  114. { TRESTConnection }
  115. function TRESTConnection.GetDataProxy: TDataProxy;
  116. begin
  117. if (FDataProxy=Nil) then
  118. FDataProxy:=DoGetDataProxy;
  119. Result:=FDataProxy;
  120. end;
  121. procedure TRESTConnection.SetupRequest(aXHR: TJSXMLHttpRequest);
  122. begin
  123. // Do nothing
  124. if aXHR=nil then ;
  125. end;
  126. function TRESTConnection.GetUpdateBaseURL(aRequest: TRecordUpdateDescriptor): String;
  127. begin
  128. Result:=BaseURL;
  129. if aRequest=nil then ;
  130. end;
  131. function TRESTConnection.GetReadBaseURL(aRequest: TDataRequest): String;
  132. begin
  133. Result:=BaseURL;
  134. if aRequest=nil then ;
  135. end;
  136. function TRESTConnection.GetPageURL(aRequest: TDataRequest): String;
  137. Var
  138. URL : String;
  139. begin
  140. URL:=GetReadBaseURL(aRequest);
  141. if (PageParam<>'') then
  142. begin
  143. if Pos('?',URL)<>0 then
  144. URL:=URL+'&'
  145. else
  146. URL:=URL+'?';
  147. URL:=URL+PageParam+'='+IntToStr(ARequest.RequestID-1);
  148. end;
  149. if Assigned(FOnGetURL) then
  150. FOnGetURL(Self,aRequest,URL);
  151. Result:=URL;
  152. end;
  153. function TRESTConnection.GetRecordUpdateURL(aRequest: TRecordUpdateDescriptor): String;
  154. Var
  155. I : integer;
  156. Base,KeyField : String;
  157. begin
  158. KeyField:='';
  159. Result:='';
  160. Base:=GetUpdateBaseURL(aRequest);
  161. if aRequest.Status in [usModified,usDeleted] then
  162. begin
  163. I:=aRequest.Dataset.Fields.Count-1;
  164. While (KeyField='') and (I>=0) do
  165. begin
  166. if pfInKey in aRequest.Dataset.Fields[i].ProviderFlags then
  167. KeyField:=aRequest.Dataset.Fields[i].FieldName;
  168. Dec(I);
  169. end;
  170. if (KeyField='') then
  171. DatabaseError('No key field',aRequest.Dataset);
  172. end;
  173. if (KeyField<>'') and (Base<>'') and (Base[Length(Base)]<>'/') then
  174. Base:=Base+'/';
  175. Case aRequest.Status of
  176. usModified,
  177. usDeleted: Result:=Base+TJSJSON.stringify(TJSObject(aRequest.Data)[KeyField]);
  178. usInserted : Result:=Base;
  179. end;
  180. If Assigned(FOnUpdateURL) then
  181. FOnUpdateURL(Self,aRequest,Result);
  182. end;
  183. function TRESTConnection.DoGetDataProxy: TDataProxy;
  184. begin
  185. Result:=TRESTDataProxy.Create(Self);
  186. end;
  187. { TRESTDataProxy }
  188. procedure TRESTDataProxy.CheckBatchComplete(aBatch: TRecordUpdateBatch);
  189. Var
  190. BatchOK : Boolean;
  191. I : Integer;
  192. begin
  193. BatchOK:=True;
  194. I:=aBatch.List.Count-1;
  195. While BatchOK and (I>=0) do
  196. begin
  197. BatchOK:=aBatch.List[I].ResolveStatus in [rsResolved,rsResolveFailed];
  198. Dec(I);
  199. end;
  200. If BatchOK and Assigned(aBatch.OnResolve) then
  201. aBatch.OnResolve(Self,aBatch);
  202. end;
  203. function TRESTDataProxy.GetUpdateDescriptorClass: TRecordUpdateDescriptorClass;
  204. begin
  205. Result:=TRESTUpdateRequest;
  206. end;
  207. function TRESTDataProxy.ProcessUpdateBatch(aBatch: TRecordUpdateBatch): Boolean;
  208. Var
  209. R : TRESTUpdateRequest;
  210. i : Integer;
  211. Method,URl : String;
  212. begin
  213. Result:=False;
  214. For I:=0 to aBatch.List.Count-1 do
  215. begin
  216. R:=aBatch.List[i] as TRESTUpdateRequest;
  217. R.FBatch:=aBatch;
  218. R.FXHR:=TJSXMLHttpRequest.New;
  219. R.FXHR.AddEventListener('load',@R.onLoad);
  220. URL:=FConnection.GetRecordUpdateURL(R);
  221. Case R.Status of
  222. usInserted :
  223. Method:='POST';
  224. usModified:
  225. Method:='PUT';
  226. usDeleted:
  227. Method:='DELETE';
  228. end;
  229. R.FXHR.open(Method,URL);
  230. R.FXHR.setRequestHeader('content-type','application/json');
  231. Connection.SetupRequest(R.FXHR);
  232. if R.Status in [usInserted,usModified] then
  233. R.FXHR.Send(TJSJSON.Stringify(R.Data))
  234. else
  235. R.FXHR.Send;
  236. end;
  237. Result:=True;
  238. end;
  239. function TRESTDataProxy.DoGetData(aRequest: TDataRequest): Boolean;
  240. Var
  241. R : TRestDataRequest;
  242. URL : String;
  243. begin
  244. Result:=False;
  245. R:=aRequest as TRestDataRequest;
  246. R.FXHR:=TJSXMLHttpRequest.New;
  247. R.FXHR.AddEventListener('load',@R.onLoad);
  248. URL:=Connection.GetPageURL(aRequest);
  249. if (URL='') then
  250. begin
  251. if loAtEOF in R.LoadOptions then
  252. R.Success:=rrEOF
  253. else
  254. begin
  255. R.Success:=rrFail;
  256. R.ErrorMsg:='No URL to get data';
  257. R.DoAfterRequest; // This will free request !
  258. end;
  259. end
  260. else
  261. begin
  262. if (loAtEOF in R.LoadOptions) and (Connection.PageParam='') then
  263. R.Success:=rrEOF
  264. else
  265. begin
  266. R.FXHR.open('GET',URL,true);
  267. Connection.SetupRequest(R.FXHR);
  268. R.FXHR.send;
  269. Result:=True;
  270. end;
  271. end;
  272. end;
  273. function TRESTDataProxy.GetDataRequest(aOptions: TLoadOptions; aAfterRequest: TDataRequestEvent; aAfterLoad: TDatasetLoadEvent): TDataRequest;
  274. begin
  275. Result:=TRestDataRequest.Create(Self,aOptions, aAfterRequest,aAfterLoad);
  276. end;
  277. constructor TRESTDataProxy.Create(AOwner: TComponent);
  278. begin
  279. Inherited;
  280. If AOwner is TRestConnection then
  281. FConnection:=TRestConnection(aOwner);
  282. end;
  283. end.