Quick.HttpServer.pas 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. { ***************************************************************************
  2. Copyright (c) 2016-2020 Kike Pérez
  3. Unit : Quick.HttpServer
  4. Description : Http Server
  5. Author : Kike Pérez
  6. Version : 1.8
  7. Created : 30/08/2019
  8. Modified : 14/02/2020
  9. This file is part of QuickLib: https://github.com/exilon/QuickLib
  10. ***************************************************************************
  11. Licensed under the Apache License, Version 2.0 (the "License");
  12. you may not use this file except in compliance with the License.
  13. You may obtain a copy of the License at
  14. http://www.apache.org/licenses/LICENSE-2.0
  15. Unless required by applicable law or agreed to in writing, software
  16. distributed under the License is distributed on an "AS IS" BASIS,
  17. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  18. See the License for the specific language governing permissions and
  19. limitations under the License.
  20. *************************************************************************** }
  21. unit Quick.HttpServer;
  22. {$i QuickLib.inc}
  23. interface
  24. uses
  25. SysUtils,
  26. IdHTTPServer,
  27. IdCustomHTTPServer,
  28. IdSSLOpenSSL,
  29. IdContext,
  30. Quick.Commons,
  31. Quick.Value,
  32. Quick.Logger.Intf,
  33. Quick.HttpServer.Types,
  34. Quick.HttpServer.Request,
  35. Quick.HttpServer.Response;
  36. type
  37. EHttpProtocolError = class(Exception);
  38. TRequestEvent = procedure(aRequest : IHttpRequest; aResponse : IHttpResponse) of object;
  39. TOnConnectEvent = procedure of object;
  40. TOnDisconnectEvent = procedure of object;
  41. IHttpServer = interface
  42. ['{3B48198A-49F7-40A5-BBFD-39C78B6FA1EA}']
  43. procedure SetOnRequest(aRequestEvent : TRequestEvent);
  44. function GetOnRequest : TRequestEvent;
  45. property OnNewRequest : TRequestEvent read GetOnRequest write SetOnRequest;
  46. function Logger : ILogger;
  47. procedure Start;
  48. procedure Stop;
  49. end;
  50. TCustomHttpServer = class(TInterfacedObject,IHttpServer)
  51. private
  52. fLogger : ILogger;
  53. fOnConnect : TOnConnectEvent;
  54. fOnDisconnect : TOnDisconnectEvent;
  55. procedure SetOnRequest(aRequestEvent : TRequestEvent);
  56. function GetOnRequest : TRequestEvent;
  57. protected
  58. fOnRequest : TRequestEvent;
  59. fHost : string;
  60. fPort : Integer;
  61. fSSLSecured : Boolean;
  62. public
  63. constructor Create(const aHost : string; aPort : Integer; aSSLEnabled : Boolean; aLogger : ILogger = nil); virtual;
  64. property Host : string read fHost;
  65. property Port : Integer read fPort;
  66. property OnNewRequest : TRequestEvent read GetOnRequest write SetOnRequest;
  67. property OnConnect : TOnConnectEvent read fOnConnect write fOnConnect;
  68. property OnDisconnect : TOnDisconnectEvent read fOnDisconnect write fOnDisconnect;
  69. function Logger : ILogger;
  70. procedure Start; virtual; abstract;
  71. procedure Stop; virtual; abstract;
  72. end;
  73. THttpServer = class(TCustomHttpServer)
  74. private
  75. fHTTPServer : TidHTTPServer;
  76. procedure OnGetRequest(aContext: TIdContext; aRequestInfo: TIdHTTPRequestInfo; aResponseInfo: TIdHTTPResponseInfo);
  77. function GetSSLIOHandler : TIdServerIOHandlerSSLOpenSSL;
  78. function OnVerifyPeer(aCertificate: TIdX509; aOk: Boolean; aDepth, aError: Integer): Boolean;
  79. function GetRequestInfo(aRequestInfo : TIdHTTPRequestInfo) : THttpRequest;
  80. procedure SetResponseInfo(aResponseInfo : TIdHTTPResponseInfo; aResponse : IHttpResponse);
  81. procedure DoOnQuerySSLPort(aPort: Word; var vUseSSL: Boolean);
  82. procedure DoConnect(aContext: TIdContext);
  83. procedure DoDisconnect;
  84. procedure OnConnect(aContext: TIdContext);
  85. procedure OnDisconnect;
  86. protected
  87. procedure ProcessRequest(aRequest: IHttpRequest; aResponse: IHttpResponse); virtual;
  88. public
  89. constructor Create(const aHost : string; aPort : Integer; aSSLEnabled : Boolean; aLogger : ILogger = nil); override;
  90. destructor Destroy; override;
  91. procedure Start; override;
  92. procedure Stop; override;
  93. end;
  94. implementation
  95. { TCustomHttpServer }
  96. constructor TCustomHttpServer.Create(const aHost : string; aPort : Integer; aSSLEnabled : Boolean; aLogger : ILogger = nil);
  97. begin
  98. if aHost.IsEmpty then fHost := '127.0.0.1'
  99. else fHost := aHost;
  100. {$IFDEF DELPHILINUX}
  101. if fHost = '127.0.0.1' then fHost := '0.0.0.0';
  102. {$ENDIF}
  103. fPort := aPort;
  104. if aLogger = nil then
  105. begin
  106. fLogger := TNullLogger.Create;
  107. end
  108. else fLogger := aLogger;
  109. fSSLSecured := aSSLEnabled;
  110. end;
  111. function TCustomHttpServer.GetOnRequest: TRequestEvent;
  112. begin
  113. Result := fOnRequest;
  114. end;
  115. procedure TCustomHttpServer.SetOnRequest(aRequestEvent: TRequestEvent);
  116. begin
  117. fOnRequest := aRequestEvent;
  118. end;
  119. function TCustomHttpServer.Logger: ILogger;
  120. begin
  121. Result := fLogger;
  122. end;
  123. { THTTPServer }
  124. constructor THTTPServer.Create(const aHost : string; aPort : Integer; aSSLEnabled : Boolean; aLogger : ILogger = nil);
  125. begin
  126. inherited Create(aHost, aPort, aSSLEnabled, aLogger);
  127. Logger.Info('HTTPServer: Indy');
  128. fHTTPServer := TIdHTTPServer.Create(nil);
  129. fHTTPServer.Bindings.Clear; //make sure there's no other bindings
  130. with fHTTPServer.Bindings.Add do
  131. begin
  132. IP := fHost;
  133. Port := fPort;
  134. end;
  135. if fSSLSecured then fHTTPServer.IOHandler := GetSSLIOHandler;
  136. fHTTPServer.OnCommandGet := OnGetRequest;
  137. //fHTTPServer.OnExecute := DoConnect;
  138. fHTTPServer.OnQuerySSLPort := DoOnQuerySSLPort;
  139. end;
  140. destructor THTTPServer.Destroy;
  141. begin
  142. if Assigned(fHTTPServer) then
  143. begin
  144. if Assigned(fHTTPServer.IOHandler) then fHTTPServer.IOHandler.Free;
  145. fHTTPServer.Free;
  146. end;
  147. inherited;
  148. end;
  149. function THTTPServer.GetSSLIOHandler : TIdServerIOHandlerSSLOpenSSL;
  150. begin
  151. Result := TIdServerIOHandlerSSLOpenSSL.Create(nil);
  152. //Result.SSLOptions.RootCertFile := '.\ca.cert.pem';
  153. Result.SSLOptions.CertFile := '.\server.cert.pem';
  154. Result.SSLOptions.KeyFile := '.\server.key.pem';
  155. Result.SSLOptions.Method := sslvSSLv23;
  156. Result.SSLOptions.Mode := sslmServer;
  157. Result.OnVerifyPeer := OnVerifyPeer;
  158. end;
  159. function THTTPServer.OnVerifyPeer(aCertificate: TIdX509; aOk: Boolean; aDepth, aError: Integer): Boolean;
  160. begin
  161. Result := aOk;
  162. end;
  163. function THttpServer.GetRequestInfo(aRequestInfo: TIdHTTPRequestInfo): THttpRequest;
  164. var
  165. i : Integer;
  166. uhost : TArray<string>;
  167. begin
  168. Result := THttpRequest.Create;
  169. if aRequestInfo.Host.Contains(':') then
  170. begin
  171. uhost := aRequestInfo.Host.Split([':']);
  172. Result.Host := uhost[0];
  173. Result.Port := StrToIntDef(uhost[1],80);
  174. end
  175. else Result.Host := aRequestInfo.Host;
  176. Result.URL := aRequestInfo.URI;
  177. Result.ClientIP := aRequestInfo.RemoteIP;
  178. Result.UnParsedParams := aRequestInfo.QueryParams;
  179. Result.SetMethodFromString(aRequestInfo.Command);
  180. Result.UserAgent := aRequestInfo.UserAgent;
  181. Result.CacheControl := aRequestInfo.CacheControl;
  182. Result.Referer := aRequestInfo.Referer;
  183. Result.Content := aRequestInfo.PostStream;
  184. Result.ContentType := aRequestInfo.ContentType;
  185. Result.ContentEncoding := aRequestInfo.ContentEncoding;
  186. Result.ContentLength := aRequestInfo.ContentLength;
  187. for i := 0 to aRequestInfo.RawHeaders.Count -1 do
  188. begin
  189. if not StrInArray(aRequestInfo.RawHeaders.Names[i],['Host','Accept-Encoding','Accept','User-Agent','Connection','Cache-Control']) then
  190. begin
  191. Result.Headers.Add(aRequestInfo.RawHeaders.Names[i],aRequestInfo.RawHeaders.Values[aRequestInfo.RawHeaders.Names[i]]);
  192. end;
  193. end;
  194. end;
  195. procedure THttpServer.SetResponseInfo(aResponseInfo: TIdHTTPResponseInfo; aResponse: IHttpResponse);
  196. var
  197. pair : TPairItem;
  198. begin
  199. for pair in aResponse.Headers do
  200. begin
  201. aResponseInfo.CustomHeaders.AddValue(pair.Name,pair.Value);
  202. end;
  203. aResponseInfo.ResponseNo := aResponse.StatusCode;
  204. aResponseInfo.ResponseText := aResponse.StatusText;
  205. aResponseInfo.ContentStream := aResponse.Content;
  206. aResponseInfo.ContentText := aResponse.ContentText;
  207. aResponseInfo.ContentType := aResponse.ContentType;
  208. //delegate stream to responseinfo
  209. aResponse.Content := nil;
  210. end;
  211. procedure THttpServer.ProcessRequest(aRequest: IHttpRequest; aResponse: IHttpResponse);
  212. begin
  213. if Assigned(fOnRequest) then fOnRequest(aRequest,aResponse);
  214. end;
  215. procedure THttpServer.DoConnect(aContext: TIdContext);
  216. begin
  217. if Assigned(fOnConnect) then fOnConnect;
  218. end;
  219. procedure THttpServer.DoDisconnect;
  220. begin
  221. if Assigned(fOnDisconnect) then fOnDisconnect;
  222. end;
  223. procedure THTTPServer.DoOnQuerySSLPort(aPort: Word; var vUseSSL: Boolean);
  224. begin
  225. vUseSSL := (aPort <> 443);
  226. end;
  227. procedure THTTPServer.OnConnect(aContext: TIdContext);
  228. begin
  229. Logger.Debug('Client connected');
  230. end;
  231. procedure THTTPServer.OnDisconnect;
  232. begin
  233. Logger.Debug('Client disconnected!');
  234. end;
  235. procedure THTTPServer.OnGetRequest(aContext: TIdContext; aRequestInfo: TIdHTTPRequestInfo; aResponseInfo: TIdHTTPResponseInfo);
  236. var
  237. request : IHttpRequest;
  238. response : IHttpResponse;
  239. begin
  240. Logger.Debug('Request: %s',[aRequestInfo.RawHTTPCommand]);
  241. request := GetRequestInfo(aRequestInfo);
  242. response := THttpResponse.Create;
  243. //process incoming Request
  244. try
  245. ProcessRequest(request,response);
  246. except
  247. on E : Exception do
  248. begin
  249. //get unexpected exception
  250. if E.ClassType <> EControlledException then
  251. begin
  252. if response.StatusCode = 200 then
  253. begin
  254. response.StatusCode := 500;
  255. response.StatusText := 'Internal server error';
  256. end;
  257. response.ContentText := e.Message;
  258. end
  259. else response.ContentText := response.ContentText + '<BR>' + e.Message;
  260. end;
  261. end;
  262. //check if need return error page
  263. if response.StatusCode > 399 then
  264. begin
  265. response.ContentText := Format('<h2>%d Error: %s</h2>',[response.StatusCode,response.StatusText])
  266. + Format('<h4>Message: %s</h4>',[response.ContentText]);
  267. end;
  268. //return response to client
  269. SetResponseInfo(aResponseInfo,response);
  270. aResponseInfo.WriteContent;
  271. end;
  272. procedure THttpServer.Start;
  273. begin
  274. fHTTPServer.Active := True;
  275. end;
  276. procedure THttpServer.Stop;
  277. begin
  278. fHTTPServer.Active := False;
  279. end;
  280. end.