simpleserver.pas 11 KB

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