simpleserver.pas 10 KB

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