Quick.HttpServer.pas 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. { ***************************************************************************
  2. Copyright (c) 2016-2019 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 : 16/10/2019
  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. fPort := aPort;
  101. if aLogger = nil then
  102. begin
  103. fLogger := TNullLogger.Create;
  104. end
  105. else fLogger := aLogger;
  106. fSSLSecured := aSSLEnabled;
  107. end;
  108. function TCustomHttpServer.GetOnRequest: TRequestEvent;
  109. begin
  110. Result := fOnRequest;
  111. end;
  112. procedure TCustomHttpServer.SetOnRequest(aRequestEvent: TRequestEvent);
  113. begin
  114. fOnRequest := aRequestEvent;
  115. end;
  116. function TCustomHttpServer.Logger: ILogger;
  117. begin
  118. Result := fLogger;
  119. end;
  120. { THTTPServer }
  121. constructor THTTPServer.Create(const aHost : string; aPort : Integer; aSSLEnabled : Boolean; aLogger : ILogger = nil);
  122. begin
  123. inherited Create(aHost, aPort, aSSLEnabled, aLogger);
  124. Logger.Info('HTTPServer: Indy');
  125. fHTTPServer := TIdHTTPServer.Create(nil);
  126. fHTTPServer.Bindings.Clear; //make sure there's no other bindings
  127. with fHTTPServer.Bindings.Add do
  128. begin
  129. IP := fHost;
  130. Port := fPort;
  131. end;
  132. if fSSLSecured then fHTTPServer.IOHandler := GetSSLIOHandler;
  133. fHTTPServer.OnCommandGet := OnGetRequest;
  134. //fHTTPServer.OnExecute := DoConnect;
  135. fHTTPServer.OnQuerySSLPort := DoOnQuerySSLPort;
  136. end;
  137. destructor THTTPServer.Destroy;
  138. begin
  139. if Assigned(fHTTPServer) then
  140. begin
  141. if Assigned(fHTTPServer.IOHandler) then fHTTPServer.IOHandler.Free;
  142. fHTTPServer.Free;
  143. end;
  144. inherited;
  145. end;
  146. function THTTPServer.GetSSLIOHandler : TIdServerIOHandlerSSLOpenSSL;
  147. begin
  148. Result := TIdServerIOHandlerSSLOpenSSL.Create(nil);
  149. //Result.SSLOptions.RootCertFile := '.\ca.cert.pem';
  150. Result.SSLOptions.CertFile := '.\server.cert.pem';
  151. Result.SSLOptions.KeyFile := '.\server.key.pem';
  152. Result.SSLOptions.Method := sslvSSLv23;
  153. Result.SSLOptions.Mode := sslmServer;
  154. Result.OnVerifyPeer := OnVerifyPeer;
  155. end;
  156. function THTTPServer.OnVerifyPeer(aCertificate: TIdX509; aOk: Boolean; aDepth, aError: Integer): Boolean;
  157. begin
  158. Result := aOk;
  159. end;
  160. function THttpServer.GetRequestInfo(aRequestInfo: TIdHTTPRequestInfo): THttpRequest;
  161. var
  162. i : Integer;
  163. uhost : TArray<string>;
  164. begin
  165. Result := THttpRequest.Create;
  166. if aRequestInfo.Host.Contains(':') then
  167. begin
  168. uhost := aRequestInfo.Host.Split([':']);
  169. Result.Host := uhost[0];
  170. Result.Port := StrToIntDef(uhost[1],80);
  171. end
  172. else Result.Host := aRequestInfo.Host;
  173. Result.URL := aRequestInfo.URI;
  174. Result.ClientIP := aRequestInfo.RemoteIP;
  175. Result.UnParsedParams := aRequestInfo.QueryParams;
  176. Result.SetMethodFromString(aRequestInfo.Command);
  177. Result.UserAgent := aRequestInfo.UserAgent;
  178. Result.CacheControl := aRequestInfo.CacheControl;
  179. Result.Referer := aRequestInfo.Referer;
  180. Result.Content := aRequestInfo.PostStream;
  181. Result.ContentType := aRequestInfo.ContentType;
  182. Result.ContentEncoding := aRequestInfo.ContentEncoding;
  183. Result.ContentLength := aRequestInfo.ContentLength;
  184. for i := 0 to aRequestInfo.RawHeaders.Count -1 do
  185. begin
  186. if not StrInArray(aRequestInfo.RawHeaders.Names[i],['Host','Accept-Encoding','Accept','User-Agent','Connection','Cache-Control']) then
  187. begin
  188. Result.Headers.Add(aRequestInfo.RawHeaders.Names[i],aRequestInfo.RawHeaders.ValueFromIndex[i]);
  189. end;
  190. end;
  191. end;
  192. procedure THttpServer.SetResponseInfo(aResponseInfo: TIdHTTPResponseInfo; aResponse: IHttpResponse);
  193. var
  194. pair : TPairItem;
  195. begin
  196. for pair in aResponse.Headers do
  197. begin
  198. aResponseInfo.CustomHeaders.AddValue(pair.Name,pair.Value);
  199. end;
  200. aResponseInfo.ResponseNo := aResponse.StatusCode;
  201. aResponseInfo.ResponseText := aResponse.StatusText;
  202. aResponseInfo.ContentStream := aResponse.Content;
  203. aResponseInfo.ContentText := aResponse.ContentText;
  204. aResponseInfo.ContentType := aResponse.ContentType;
  205. //delegate stream to responseinfo
  206. aResponse.Content := nil;
  207. end;
  208. procedure THttpServer.ProcessRequest(aRequest: IHttpRequest; aResponse: IHttpResponse);
  209. begin
  210. if Assigned(fOnRequest) then fOnRequest(aRequest,aResponse);
  211. end;
  212. procedure THttpServer.DoConnect(aContext: TIdContext);
  213. begin
  214. if Assigned(fOnConnect) then fOnConnect;
  215. end;
  216. procedure THttpServer.DoDisconnect;
  217. begin
  218. if Assigned(fOnDisconnect) then fOnDisconnect;
  219. end;
  220. procedure THTTPServer.DoOnQuerySSLPort(aPort: Word; var vUseSSL: Boolean);
  221. begin
  222. vUseSSL := (aPort <> 443);
  223. end;
  224. procedure THTTPServer.OnConnect(aContext: TIdContext);
  225. begin
  226. Logger.Debug('Client connected');
  227. end;
  228. procedure THTTPServer.OnDisconnect;
  229. begin
  230. Logger.Debug('Client disconnected!');
  231. end;
  232. procedure THTTPServer.OnGetRequest(aContext: TIdContext; aRequestInfo: TIdHTTPRequestInfo; aResponseInfo: TIdHTTPResponseInfo);
  233. var
  234. request : IHttpRequest;
  235. response : IHttpResponse;
  236. begin
  237. Logger.Debug('Request: %s',[aRequestInfo.RawHTTPCommand]);
  238. request := GetRequestInfo(aRequestInfo);
  239. response := THttpResponse.Create;
  240. //process incoming Request
  241. try
  242. ProcessRequest(request,response);
  243. except
  244. on E : Exception do
  245. begin
  246. //get unexpected exception
  247. if E.ClassType <> EControlledException then
  248. begin
  249. if response.StatusCode = 200 then
  250. begin
  251. response.StatusCode := 500;
  252. response.StatusText := 'Internal server error';
  253. end;
  254. response.ContentText := e.Message;
  255. end
  256. else response.ContentText := response.ContentText + '<BR>' + e.Message;
  257. end;
  258. end;
  259. //check if need return error page
  260. if response.StatusCode > 399 then
  261. begin
  262. response.ContentText := Format('<h2>%d Error: %s</h2>',[response.StatusCode,response.StatusText])
  263. + Format('<h4>Message: %s</h4>',[response.ContentText]);
  264. end;
  265. //return response to client
  266. SetResponseInfo(aResponseInfo,response);
  267. aResponseInfo.WriteContent;
  268. end;
  269. procedure THttpServer.Start;
  270. begin
  271. fHTTPServer.Active := True;
  272. end;
  273. procedure THttpServer.Stop;
  274. begin
  275. fHTTPServer.Active := False;
  276. end;
  277. end.