simpleserver.pas 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410
  1. {
  2. This file is part of the Free Pascal run time library.
  3. Copyright (c) 2019 by the Free Pascal development team
  4. Sample HTTP server application
  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. {$mode objfpc}
  12. {$h+}
  13. { $DEFINE USEGNUTLS}
  14. { $DEFINE USEMICROHTTP}
  15. program simpleserver;
  16. {$IFDEF USEMICROHTTP}
  17. {$UNDEF USEGNUTLS}
  18. {$ENDIF}
  19. uses
  20. {$IFNDEF USEMICROHTTP}
  21. {$ifdef USEGNUTLS}
  22. gnutlssockets,
  23. {$else}
  24. opensslsockets,
  25. {$endif}
  26. custhttpapp,
  27. {$ELSE}
  28. {$ifdef unix}
  29. cthreads,
  30. {$endif}
  31. custmicrohttpapp,
  32. {$ENDIF}
  33. {$ifdef unix}
  34. baseunix,
  35. {$endif}
  36. sysutils,Classes, jsonparser, strutils, inifiles, sslbase, httproute, httpdefs, fpmimetypes, fpwebfile, fpwebproxy, webutil;
  37. Type
  38. { THTTPApplication }
  39. {$IFDEF USEMICROHTTP}
  40. TParentApp = TCustomMicroHTTPApplication;
  41. {$ELSE}
  42. TParentApp = TCustomHTTPApplication;
  43. {$ENDIF}
  44. THTTPApplication = Class(TParentApp)
  45. private
  46. FAPISecret : String;
  47. FBaseDir: string;
  48. FIndexPageName: String;
  49. FInterfaceAddress: String;
  50. FMimeFile: String;
  51. FNoIndexPage: Boolean;
  52. FQuiet: Boolean;
  53. FBackground : Boolean;
  54. FPassword : string;
  55. FEcho : Boolean;
  56. FMaxAge : Integer;
  57. procedure AddProxy(const aProxyDef: String);
  58. procedure DoEcho(ARequest: TRequest; AResponse: TResponse);
  59. procedure DoProxyLog(Sender: TObject; const aMethod, aLocation, aFromURL, aToURL: String);
  60. procedure Doquit(ARequest: TRequest; AResponse: TResponse);
  61. procedure LoadMimeTypes;
  62. procedure ProcessOptions;
  63. procedure ReadConfigFile(const ConfigFile: string);
  64. procedure Usage(Msg: String);
  65. procedure Writeinfo;
  66. published
  67. procedure DoLog(EventType: TEventType; const Msg: String); override;
  68. Procedure DoRun; override;
  69. property Quiet : Boolean read FQuiet Write FQuiet;
  70. Property MimeFile : String Read FMimeFile Write FMimeFile;
  71. Property BaseDir : string Read FBaseDir Write FBaseDir;
  72. Property NoIndexPage : Boolean Read FNoIndexPage Write FNoIndexPage;
  73. Property IndexPageName : String Read FIndexPageName Write FIndexPageName;
  74. Property InterfaceAddress : String Read FInterfaceAddress Write FInterfaceAddress;
  75. end;
  76. Var
  77. Application : THTTPApplication;
  78. { THTTPApplication }
  79. procedure THTTPApplication.DoEcho(ARequest: TRequest; AResponse: TResponse);
  80. Var
  81. L : TStrings;
  82. begin
  83. L:=TStringList.Create;
  84. try
  85. L.AddStrings(['<!doctype html>',
  86. '<html>',
  87. '<head>',
  88. '<title>Echo request</title>',
  89. '</head>',
  90. '<body>'
  91. ]);
  92. DumpRequest(aRequest,L);
  93. L.AddStrings(['</body>','</html>']);
  94. AResponse.Content:=L.Text;
  95. AResponse.SendResponse;
  96. finally
  97. L.Free;
  98. end;
  99. end;
  100. procedure THTTPApplication.Doquit(ARequest: TRequest; AResponse: TResponse);
  101. Var
  102. PWD : String;
  103. begin
  104. PWD:=ARequest.QueryFields.Values['password'];
  105. if PWD='' then
  106. ARequest.ContentFields.Values['password'];
  107. if PWD=FPassword then
  108. begin
  109. AResponse.Content:='OK';
  110. AResponse.SendContent;
  111. Terminate;
  112. end
  113. else
  114. begin
  115. AResponse.Code:=403;
  116. AResponse.CodeText:='Forbidden';
  117. AResponse.SendContent;
  118. end;
  119. end;
  120. procedure THTTPApplication.DoLog(EventType: TEventType; const Msg: String);
  121. begin
  122. if Quiet then
  123. exit;
  124. if IsConsole then
  125. Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',EventType,'] ',Msg)
  126. else
  127. inherited DoLog(EventType, Msg);
  128. end;
  129. procedure THTTPApplication.DoProxyLog(Sender: TObject; const aMethod, aLocation, aFromURL, aToURL: String);
  130. Var
  131. Msg : String;
  132. begin
  133. if Quiet then
  134. exit;
  135. Msg:=Format('(Proxy redirect) location: %s, Method: %s, From: %s, to: %s',[aLocation,aMethod,aFromURl,atoURL]);
  136. if IsConsole then
  137. Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',etInfo,'] ',Msg)
  138. else
  139. inherited DoLog(etInfo, Msg);
  140. end;
  141. procedure THTTPApplication.Usage(Msg : String);
  142. begin
  143. if (Msg<>'') then
  144. Writeln('Error: ',Msg);
  145. Writeln('Usage ',ExtractFileName(ParamStr(0)),' [options] ');
  146. Writeln('Where options is one or more of : ');
  147. Writeln('-A --api=path,secret Activate location API on path, using secret as accepted bearer token.');
  148. Writeln('-a --max-age=age Set max-age expiry header on returned file requests.');
  149. Writeln('-c --config=file Ini configuration file (default: simpleserver.ini)');
  150. {$ifdef unix}
  151. Writeln('-b --background fork to background');
  152. {$endif}
  153. Writeln('-d --directory=dir Base directory from which to serve files.');
  154. Writeln(' Default is current working directory: ',GetCurrentDir);
  155. Writeln('-e --echo Activate /echo URL.');
  156. Writeln('-h --help This help text');
  157. Writeln('-H --hostname=NAME Set hostname for self-signed SSL certificate');
  158. Writeln('-i --indexpage=name Directory index page to use (default: index.html)');
  159. Writeln('-I --interface=IP Listen on this interface address only.');
  160. Writeln('-m --mimetypes=file Path of mime.types. Loaded in addition to OS known types');
  161. Writeln('-n --noindexpage Do not allow index page.');
  162. Writeln('-p --port=NNNN TCP/IP port to listen on (default is 3000)');
  163. Writeln('-q --quiet Do not write diagnostic messages');
  164. Writeln('-Q --quit=PWD Register /quit URL. Send request with password variable equal to PWD to stop');
  165. Writeln('-s --ssl Use SSL');
  166. Writeln('-x --proxy=proxydef Add proxy definition. Definition is of form:');
  167. Writeln(' name:BaseURL');
  168. Writeln('');
  169. Writeln('Config file is ini file, section [Server]. Key names are long option names');
  170. Writeln('Proxies are defined in section [Proxy], Key is name, value is URL');
  171. Writeln('Locations are defined in section [Locations], Key is location name, value is path');
  172. Halt(Ord(Msg<>''));
  173. end;
  174. procedure THTTPApplication.LoadMimeTypes;
  175. begin
  176. MimeTypes.LoadKnownTypes;
  177. if MimeFile<>'' then
  178. begin
  179. MimeTypesFile:=MimeFile;
  180. if (MimeTypesFile<>'') and not FileExists(MimeTypesFile) then
  181. begin
  182. Log(etWarning,'mimetypes file not found: '+MimeTypesFile);
  183. MimeTypesFile:='';
  184. end;
  185. end;
  186. If MimeTypesFile<>'' then
  187. MimeTypes.LoadFromFile(MimeTypesFile);
  188. end;
  189. procedure THTTPApplication.AddProxy(const aProxyDef: String);
  190. Var
  191. P : Integer;
  192. N,URL : String;
  193. begin
  194. P:=Pos(':',aProxyDef);
  195. If P=0 then Raise
  196. EHTTP.CreateFmt('Invalid proxy definition: %s',[aProxyDef]);
  197. N:=Copy(aProxyDef,1,P-1);
  198. URL:=Copy(aProxyDef,P+1,Length(aProxyDef));
  199. ProxyManager.RegisterLocation(N,URL).AppendPathInfo:=True;
  200. end;
  201. procedure THTTPApplication.ReadConfigFile(const ConfigFile: string);
  202. Const
  203. SConfig = 'Server';
  204. SProxy = 'Proxy';
  205. SLocations = 'Locations';
  206. KeyPort = 'Port';
  207. KeyInterface = 'Interface';
  208. KeyDir = 'Directory';
  209. KeyIndexPage = 'IndexPage';
  210. KeyHostName = 'hostname';
  211. keyMimetypes = 'mimetypes';
  212. KeySSL = 'SSL';
  213. KeyQuiet = 'quiet';
  214. KeyQuit = 'quit';
  215. KeyEcho = 'echo';
  216. KeyNoIndexPage = 'noindexpage';
  217. KeyBackground = 'background';
  218. KeyMaxAge = 'MaxAge';
  219. KeyAPI = 'API';
  220. Var
  221. L : TStringList;
  222. P,U : String;
  223. I : Integer;
  224. begin
  225. if (ConfigFile='') or Not FileExists(ConfigFile) then exit;
  226. L:=Nil;
  227. With TMemIniFile.Create(ConfigFile) do
  228. try
  229. BaseDir:=ReadString(SConfig,KeyDir,BaseDir);
  230. Port:=ReadInteger(SConfig,KeyPort,Port);
  231. InterfaceAddress:=ReadString(SConfig,KeyInterface,InterfaceAddress);
  232. Quiet:=ReadBool(SConfig,KeyQuiet,Quiet);
  233. MimeFile:=ReadString(SConfig,keyMimetypes,MimeFile);
  234. NoIndexPage:=ReadBool(SConfig,KeyNoIndexPage,NoIndexPage);
  235. IndexPageName:=ReadString(SConfig,KeyIndexPage,IndexPageName);
  236. HostName:=ReadString(SConfig,KeyHostName,HostName);
  237. UseSSL:=ReadBool(SConfig,KeySSL,UseSSL);
  238. FBackground:=ReadBool(SConfig,Keybackground,FBackGround);
  239. FPassword:=ReadString(SConfig,KeyQuit,FPassword);
  240. FEcho:=ReadBool(SConfig,KeyEcho,FEcho);
  241. FMaxAge:=ReadInteger(SConfig,KeyMaxAge,FMaxAge);
  242. FAPISecret:=ReadString(SConfig,keyAPI,'');
  243. L:=TstringList.Create;
  244. ReadSectionValues(SProxy,L,[]);
  245. For I:=0 to L.Count-1 do
  246. begin
  247. L.GetNameValue(I,P,U);
  248. if (P<>'') and (U<>'') then
  249. ProxyManager.RegisterLocation(P,U).AppendPathInfo:=true;
  250. end;
  251. L.Clear;
  252. ReadSectionValues(SLocations,L,[]);
  253. For I:=0 to L.Count-1 do
  254. begin
  255. L.GetNameValue(I,P,U);
  256. if (P<>'') and (U<>'') then
  257. RegisterFileLocation(P,U);
  258. end;
  259. finally
  260. L.Free;
  261. Free;
  262. end;
  263. end;
  264. procedure THTTPApplication.ProcessOptions;
  265. Var
  266. S : String;
  267. begin
  268. for S in GetOptionValues('x','proxy') do
  269. AddProxy(S);
  270. FAPISecret:=GetOptionValue('A','api');
  271. FEcho:=HasOption('e','echo');
  272. Quiet:=HasOption('q','quiet');
  273. FPassword:=GetOptionValue('Q','quit');
  274. Port:=StrToIntDef(GetOptionValue('p','port'),Port);
  275. LoadMimeTypes;
  276. if HasOption('d','directory') then
  277. BaseDir:=GetOptionValue('d','directory');
  278. UseSSL:=HasOption('s','ssl');
  279. if HasOption('H','hostname') then
  280. HostName:=GetOptionValue('H','hostname');
  281. if HasOption('n','noindexpage') then
  282. NoIndexPage:=True
  283. else
  284. IndexPageName:=GetOptionValue('i','indexpage');
  285. if HasOption('I','interface') then
  286. InterfaceAddress:=GetOptionValue('I','interface');
  287. FMaxAge:=StrToIntDef(GetOptionValue('a','max-age'),FMaxAge);
  288. FBackground:=HasOption('b','background');
  289. end;
  290. procedure THTTPApplication.Writeinfo;
  291. Var
  292. I : Integer;
  293. begin
  294. Log(etInfo,'Listening on port %d, serving files from directory: %s (using SSL: %s)',[Port,BaseDir,BoolToStr(UseSSL,'true','false')]);
  295. For I:=0 to ProxyManager.LocationCount-1 do
  296. with ProxyManager.Locations[i] do
  297. Log(etInfo,'Proxy location /proxy/%s redirects to %s',[Path,URL]);
  298. if not NoIndexPage then
  299. Log(etInfo,'Using index page %s',[IndexPageName]);
  300. Log(etInfo,'Location REST API '+IfThen(FAPISecret<>'','','NOT ')+'activated.');
  301. end;
  302. procedure THTTPApplication.DoRun;
  303. Var
  304. S,ConfigFile : String;
  305. begin
  306. FMaxAge:=31557600;
  307. S:=Checkoptions('hqd:ni:p:sH:m:x:c:beQ:a:A:',['help','quiet','noindexpage','directory:','port:','indexpage:','ssl','hostname:','mimetypes:','proxy:','config:','background','echo','quit:','max-age:','api:']);
  308. if (S<>'') or HasOption('h','help') then
  309. usage(S);
  310. if HasOption('c','config') then
  311. ConfigFile:=GetOptionValue('c','config')
  312. else
  313. ConfigFile:='simpleserver.ini';
  314. ReadConfigFile(ConfigFile);
  315. ProcessOptions;
  316. if FBackground then
  317. begin
  318. {$ifdef unix}
  319. if FPFork>0 then Halt(0);
  320. {$else}
  321. Log(etError,'Background option not supported');
  322. {$endif}
  323. end;
  324. if FPassword<>'' then
  325. HTTPRouter.RegisterRoute('/quit',rmAll,@Doquit,False);
  326. if FEcho then
  327. HTTPRouter.RegisterRoute('/echo',rmAll,@DoEcho,False);
  328. if ProxyManager.LocationCount>0 then
  329. begin
  330. TProxyWebModule.RegisterModule('Proxy',True);
  331. ProxyManager.OnLog:=@DoProxyLog;
  332. end;
  333. DefaultCacheControlMaxAge:=FMaxAge; // one year by default
  334. if BaseDir='' then
  335. BaseDir:=GetCurrentDir;
  336. if (BaseDir<>'') then
  337. BaseDir:=IncludeTrailingPathDelimiter(BaseDir);
  338. if FAPISecret<>'' then
  339. TFPWebFileLocationAPIModule.RegisterFileLocationAPI(ExtractWord(1,FAPISecret,[',']),ExtractWord(2,FAPISecret,[',']));
  340. TSimpleFileModule.RegisterDefaultRoute;
  341. TSimpleFileModule.BaseDir:=BaseDir;
  342. TSimpleFileModule.OnLog:=@Log;
  343. If not NoIndexPage then
  344. begin
  345. if (IndexPageName='') then
  346. IndexPageName:='index.html';
  347. TSimpleFileModule.IndexPageName:=IndexPageName;
  348. end;
  349. if not Quiet then
  350. WriteInfo;
  351. if InterfaceAddress<>'' then
  352. HTTPHandler.Address:=InterfaceAddress;
  353. inherited;
  354. end;
  355. begin
  356. Application:=THTTPApplication.Create(Nil);
  357. Application.Initialize;
  358. Application.Run;
  359. Application.Free;
  360. end.