fpwebproxy.pp 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by the Free Pascal development team
  4. Classes to implement a proxy mechanism.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$IFNDEF FPC_DOTTEDUNITS}
  12. unit fpwebproxy;
  13. {$ENDIF FPC_DOTTEDUNITS}
  14. {$mode objfpc}{$H+}
  15. // Define this to output debug info on console
  16. { $DEFINE DEBUGPROXY}
  17. interface
  18. {$IFDEF FPC_DOTTEDUNITS}
  19. uses
  20. System.Classes, System.SysUtils, FpWeb.Http.Base, FpWeb.Http.Defs, FpWeb.Http.Protocol, FpWeb.Http.Client;
  21. {$ELSE FPC_DOTTEDUNITS}
  22. uses
  23. Classes, SysUtils, fphttp, httpdefs, httpprotocol, fphttpclient;
  24. {$ENDIF FPC_DOTTEDUNITS}
  25. Type
  26. TProxyRequestLog = Procedure(Sender : TObject; Const Method,Location,FromURL,ToURL : String) of object;
  27. { TProxyLocation }
  28. TProxyLocation = Class(TCollectionItem)
  29. private
  30. FAppendPathInfo: Boolean;
  31. FEnabled: Boolean;
  32. FPath: String;
  33. FRedirect: Boolean;
  34. FURL: String;
  35. Published
  36. Property Path : String Read FPath Write FPath;
  37. Property URL : String Read FURL Write FURL;
  38. Property Enabled : Boolean Read FEnabled Write FEnabled;
  39. Property Redirect : Boolean Read FRedirect Write FRedirect;
  40. Property AppendPathInfo : Boolean Read FAppendPathInfo Write FAppendPathInfo;
  41. end;
  42. { TProxyLocations }
  43. TProxyLocations = Class(TCollection)
  44. private
  45. function GetL(AIndex : Integer): TProxyLocation;
  46. procedure SetL(AIndex : Integer; AValue: TProxyLocation);
  47. Public
  48. Function IndexOfLocation(Const APath : String) : Integer;
  49. Function FindLocation(Const APath : String) : TProxyLocation;
  50. Property Locations [AIndex : Integer] : TProxyLocation Read GetL Write SetL; default;
  51. end;
  52. { TProxyWebModule }
  53. TProxyWebModule = Class(TCustomHTTPModule)
  54. protected
  55. Procedure DoLog(Const aMethod,aLocation,aFromURL,aToURL : String);
  56. procedure ClientToResponse(T: TFPHTTPClient; aResponse: TResponse); virtual;
  57. procedure RequestToClient(T: TFPHTTPClient; aRequest: TRequest); virtual;
  58. procedure ReRouteRequest(L: TProxyLocation; ARequest: TRequest; AResponse: TResponse);virtual;
  59. Public
  60. Procedure HandleRequest(ARequest : TRequest; AResponse : TResponse); override;
  61. Published
  62. Property CORS;
  63. Property BaseURL;
  64. Property AfterInitModule;
  65. Property Kind;
  66. end;
  67. { TProxyManager }
  68. TProxyManager = Class(TObject)
  69. private
  70. FLocations : TProxyLocations;
  71. FOnLog: TProxyRequestLog;
  72. function GetLocation(AIndex : Integer): TProxyLocation;
  73. function GetLocationCount: Integer;
  74. Public
  75. Constructor create;
  76. Destructor Destroy; override;
  77. Function RegisterLocation(Const APath,AURL : String) : TProxyLocation;
  78. Function UnRegisterLocation(Const APath : String) : boolean;
  79. Function FindLocation(Const APath : String) : TProxyLocation;
  80. Property LocationCount : Integer Read GetLocationCount;
  81. Property Locations[AIndex : Integer] : TProxyLocation Read GetLocation;
  82. Property OnLog : TProxyRequestLog Read FOnLog Write FOnLog;
  83. end;
  84. EWAProxy = Class(Exception);
  85. Function ProxyManager: TProxyManager;
  86. implementation
  87. {$IFDEF FPC_DOTTEDUNITS}
  88. uses System.StrUtils;
  89. {$ELSE FPC_DOTTEDUNITS}
  90. uses StrUtils;
  91. {$ENDIF FPC_DOTTEDUNITS}
  92. Resourcestring
  93. SErrDuplicateProxy = 'Duplicate proxy location: "%s"';
  94. Var
  95. PM : TProxyManager;
  96. Function ProxyManager: TProxyManager;
  97. begin
  98. If PM=Nil then
  99. PM:=TProxyManager.Create;
  100. Result:=PM;
  101. end;
  102. { TProxyManager }
  103. function TProxyManager.GetLocation(AIndex : Integer): TProxyLocation;
  104. begin
  105. Result:=FLocations[AIndex];
  106. end;
  107. function TProxyManager.GetLocationCount: Integer;
  108. begin
  109. Result:=FLocations.Count;
  110. end;
  111. constructor TProxyManager.create;
  112. begin
  113. inherited create;
  114. FLocations:=TProxyLocations.Create(TProxyLocation);
  115. end;
  116. destructor TProxyManager.Destroy;
  117. begin
  118. FreeAndNil(FLocations);
  119. inherited Destroy;
  120. end;
  121. function TProxyManager.RegisterLocation(const APath, AURL: String
  122. ): TProxyLocation;
  123. begin
  124. Result:=FLocations.FindLocation(APAth);
  125. if Result<>Nil then
  126. Raise EWAProxy.CreateFmt(SErrDuplicateProxy,[APath]);
  127. Result:=FLocations.Add as TProxyLocation;
  128. Result.Path:=APath;
  129. Result.URL:=AURL;
  130. Result.Enabled:=True;
  131. end;
  132. function TProxyManager.UnRegisterLocation(const APath : String): boolean;
  133. Var
  134. l : TProxyLocation;
  135. begin
  136. L:=FLocations.FindLocation(APath);
  137. Result:=L<>Nil;
  138. If Result then
  139. L.Free;
  140. end;
  141. function TProxyManager.FindLocation(const APath: String): TProxyLocation;
  142. begin
  143. Result:=FLocations.FindLocation(APath);
  144. end;
  145. { TProxyLocations }
  146. function TProxyLocations.GetL(AIndex : Integer): TProxyLocation;
  147. begin
  148. Result:=Items[AIndex] as TProxyLocation;
  149. end;
  150. procedure TProxyLocations.SetL(AIndex : Integer; AValue: TProxyLocation);
  151. begin
  152. Items[AIndex]:=AValue;
  153. end;
  154. function TProxyLocations.IndexOfLocation(const APath: String): Integer;
  155. begin
  156. Result:=Count-1;
  157. While (Result>=0) and (CompareText(GetL(Result).Path,APath)<>0) do
  158. Dec(Result);
  159. end;
  160. function TProxyLocations.FindLocation(const APath: String): TProxyLocation;
  161. Var
  162. I : Integer;
  163. begin
  164. I:=IndexOfLocation(APath);
  165. if (I=-1) then
  166. Result:=Nil
  167. else
  168. Result:=GetL(I);
  169. end;
  170. { TProxyWebModule }
  171. procedure TProxyWebModule.RequestToClient(T : TFPHTTPClient; aRequest : TRequest);
  172. Var
  173. H : THeader;
  174. I : Integer;
  175. N,V : String;
  176. begin
  177. // Transfer known headers
  178. for H in THeader do
  179. if (hdRequest in HTTPHeaderDirections[H]) then
  180. if aRequest.HeaderIsSet(H) then
  181. if H<>hhHost then
  182. begin
  183. {$ifdef DEBUGPROXY}Writeln('Sending header: ',HTTPHeaderNames[H],': ',aRequest.GetHeader(H));{$ENDIF}
  184. T.AddHeader(HTTPHeaderNames[H],aRequest.GetHeader(H));
  185. end;
  186. // Transfer custom headers
  187. For I:=0 to aRequest.CustomHeaders.Count-1 do
  188. begin
  189. aRequest.CustomHeaders.GetNameValue(I,N,V);
  190. {$ifdef DEBUGPROXY}Writeln('Sending custom header: ',N,': ',V);{$ENDIF}
  191. T.AddHeader(N,V);
  192. end;
  193. if (Length(ARequest.Content)>0) then
  194. begin
  195. T.RequestBody:=TMemoryStream.Create;
  196. T.RequestBody.WriteBuffer(ARequest.Content[1],Length(ARequest.Content));
  197. T.RequestBody.Position:=0;
  198. end;
  199. end;
  200. procedure TProxyWebModule.DoLog(const aMethod,aLocation, aFromURL, aToURL: String);
  201. begin
  202. If Assigned(ProxyManager) and Assigned(ProxyManager.OnLog) then;
  203. ProxyManager.OnLog(Self,aMethod,aLocation,aFromURl,aToURL);
  204. end;
  205. procedure TProxyWebModule.ClientToResponse(T : TFPHTTPClient; aResponse : TResponse);
  206. Var
  207. N,H,V : String;
  208. HT : THeader;
  209. begin
  210. for N in T.ResponseHeaders do
  211. begin
  212. H:=ExtractWord(1,N,[':']);
  213. HT:=HeaderType(H);
  214. if not (HT in [hhContentLength]) then
  215. begin
  216. V:=Trim(ExtractWord(2,N,[':']));
  217. {$IFDEF DEBUGPROXY}Writeln('Returning header: ',N);{$ENDIF}
  218. AResponse.SetCustomHeader(H,V);
  219. end;
  220. end;
  221. AResponse.Code:=T.ResponseStatusCode;
  222. AResponse.CodeText:=T.ResponseStatusText;
  223. AResponse.ContentLength:=AResponse.ContentStream.Size;
  224. end;
  225. procedure TProxyWebModule.ReRouteRequest(L : TProxyLocation; ARequest: TRequest; AResponse: TResponse);
  226. Var
  227. T : TFPHTTPClient;
  228. P,URL : String;
  229. begin
  230. URL:=L.URL;
  231. if L.AppendPathInfo then
  232. begin
  233. P:=ARequest.GetNextPathInfo;
  234. While P<>'' do
  235. begin
  236. URL:=IncludeHTTPPathDelimiter(URL)+P;
  237. P:=ARequest.GetNextPathInfo;
  238. end;
  239. end;
  240. if (ARequest.QueryString<>'') then
  241. URL:=URL+'?'+ARequest.QueryString;
  242. DoLog(aRequest.Method, L.Path,ARequest.URL, URL);
  243. T:=TFPHTTPClient.Create(Self);
  244. try
  245. RequestToClient(T,aRequest);
  246. aResponse.FreeContentStream:=True;
  247. aResponse.ContentStream:=TMemoryStream.Create;
  248. T.AllowRedirect:=True;
  249. T.HTTPMethod(ARequest.Method,URL,AResponse.ContentStream,[]);
  250. ClientToResponse(T,aResponse);
  251. AResponse.SendContent;
  252. finally
  253. T.RequestBody.Free;
  254. T.Free;
  255. end;
  256. end;
  257. procedure TProxyWebModule.HandleRequest(ARequest: TRequest; AResponse: TResponse);
  258. Var
  259. P : String;
  260. L : TProxyLocation;
  261. begin
  262. P:=ARequest.GetNextPathInfo;
  263. L:=ProxyManager.FindLocation(P);
  264. if (L=Nil) or (Not L.Enabled) then
  265. begin
  266. AResponse.Code:=404;
  267. AResponse.CodeText:='Location not found : '+P;
  268. AResponse.SendContent;
  269. end
  270. else if L.Redirect then
  271. begin
  272. DoLog(L.Path,aRequest.method, ARequest.URL, L.URL);
  273. AResponse.SendRedirect(L.URL);
  274. AResponse.SendContent;
  275. end
  276. else
  277. begin
  278. ReRouteRequest(L,ARequest,AResponse);
  279. if not AResponse.ContentSent then
  280. AResponse.SendContent;
  281. end;
  282. end;
  283. finalization
  284. FreeAndNil(PM);
  285. end.