httpcompiler.pp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837
  1. unit httpcompiler;
  2. {$mode objfpc}
  3. {$H+}
  4. interface
  5. uses
  6. {$ifdef unix}baseunix,{$endif}
  7. sysutils, classes, fpjson, contnrs, syncobjs, fpmimetypes, custhttpapp, inifiles,
  8. fpwebproxy, webutil, fpwebfile, httproute, httpdefs, dirwatch, Pas2JSFSCompiler,
  9. Pas2JSCompilerCfg, ssockets;
  10. Const
  11. HTTPCompilerVersion = '1.0';
  12. nErrTooManyThreads = -1;
  13. nExitCodeSocketError = 1;
  14. Type
  15. TDirWatcher = Class;
  16. THTTPCompilerApplication = Class;
  17. { TCompileItem }
  18. TCompileItem = Class(TCollectionItem)
  19. private
  20. FBaseDir: string;
  21. FConfigFile: String;
  22. FFileName: string;
  23. FOutput : TStrings;
  24. FOptions : TStrings;
  25. FSuccess: Boolean;
  26. FThread: TThread;
  27. function GetOptions: TStrings;
  28. function GetOutput: TStrings;
  29. Public
  30. Destructor Destroy; override;
  31. Property BaseDir : string Read FBaseDir Write FBaseDir;
  32. Property FileName : string Read FFileName Write FFileName;
  33. Property ConfigFile: String Read FConfigFile Write FConfigFile;
  34. Property Options : TStrings Read GetOptions;
  35. Property Output : TStrings Read GetOutput;
  36. Property Thread : TThread Read FThread;
  37. Property Success : Boolean Read FSuccess;
  38. end;
  39. { TCompiles }
  40. TCompiles = Class(TCollection)
  41. private
  42. function GetC(AIndex : Integer): TCompileItem;
  43. Public
  44. Property Compiles[AIndex : Integer] : TCompileItem Read GetC; default;
  45. end;
  46. { TCompileThread }
  47. TCompileThread = class(TThread)
  48. private
  49. FApp : THTTPCompilerApplication;
  50. FItem: TCompileItem;
  51. procedure DoCompilerLog(Sender: TObject; const Msg: String);
  52. procedure SetItem(AValue: TCompileItem);
  53. Public
  54. Constructor create(App : THTTPCompilerApplication; aItem : TCompileItem);
  55. Procedure Execute; override;
  56. Property Item : TCompileItem read FItem write SetItem;
  57. end;
  58. { TDirWatcher }
  59. TDirWatcher = Class(TComponent)
  60. Private
  61. FApp : THTTPCompilerApplication;
  62. FDW : TDirWatch;
  63. procedure DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
  64. Public
  65. Constructor Create(App : THTTPCompilerApplication; ADir : String);overload;
  66. Destructor Destroy; override;
  67. end;
  68. { THTTPCompilerApplication }
  69. THTTPCompilerApplication = Class(TCustomHTTPApplication)
  70. private
  71. FAPI: String;
  72. FBaseDir: String;
  73. FConfigFile: String;
  74. FIndexPageName: String;
  75. FNoIndexPage: Boolean;
  76. FProjectFile: String;
  77. FStatusLock : TCriticalSection;
  78. FQuiet: Boolean;
  79. FWatch: Boolean;
  80. FDW : TDirWatcher;
  81. FStatusList : TFPObjectList;
  82. FCompiles : TCompiles;
  83. FServeOnly : Boolean;
  84. FMimeFile : String;
  85. FBackground:boolean;
  86. FPassword:String;
  87. FEcho:Boolean;
  88. FMaxAge: integer;
  89. FInterfaceAddress : String;
  90. procedure AddToStatus(O: TJSONObject);
  91. procedure DoEcho(ARequest: TRequest; AResponse: TResponse);
  92. procedure DoProxyLog(Sender: TObject; const aMethod, aLocation, aFromURL, aToURL: String);
  93. procedure Doquit(ARequest: TRequest; AResponse: TResponse);
  94. function HandleCompileOptions(aDir: String): Boolean;
  95. function ProcessOptions: Boolean;
  96. procedure ReadConfigFile(const ConfigFile: string);
  97. Procedure ReportBuilding(AItem : TCompileItem);
  98. Procedure ReportBuilt(AItem : TCompileItem);
  99. Procedure AddToStatus(AEntry : TDirectoryEntry; AEvents : TFileEvents);
  100. procedure DoStatusRequest(ARequest: TRequest; AResponse: TResponse);
  101. procedure DoRecompile(ARequest: TRequest; AResponse: TResponse);
  102. function ScheduleCompile(const aProjectFile: String; Options : TStrings = Nil): Integer;
  103. procedure StartWatch(ADir: String);
  104. protected
  105. procedure Usage(Msg: String); virtual;
  106. function GetDefaultMimeTypesFile: string; virtual;
  107. procedure LoadDefaultMimeTypes; virtual;
  108. public
  109. Constructor Create(AOWner : TComponent); override;
  110. Destructor Destroy; override;
  111. procedure DoLog(EventType: TEventType; const Msg: String); override;
  112. Procedure DoRun; override;
  113. Property API : String Read FAPI Write FAPI;
  114. property Quiet : Boolean read FQuiet Write FQuiet;
  115. Property Watch : Boolean Read FWatch Write FWatch;
  116. Property ProjectFile : String Read FProjectFile Write FProjectFile;
  117. Property ConfigFile : String Read FConfigFile Write FConfigFile;
  118. Property BaseDir : String Read FBaseDir;
  119. Property ServeOnly : Boolean Read FServeOnly;
  120. Property MimeFile : String Read FMimeFile;
  121. Property NoIndexPage : Boolean Read FNoIndexPage Write FNoIndexPage;
  122. Property IndexPageName : String Read FIndexPageName Write FIndexPageName;
  123. Property InterfaceAddress : String Read FInterfaceAddress Write FInterfaceAddress;
  124. end;
  125. Implementation
  126. uses strutils;
  127. { TCompileThread }
  128. procedure TCompileThread.SetItem(AValue: TCompileItem);
  129. begin
  130. if FItem=AValue then Exit;
  131. FItem:=AValue;
  132. end;
  133. procedure TCompileThread.DoCompilerLog(Sender: TObject; const Msg: String);
  134. begin
  135. If Assigned(Item) then
  136. Item.Output.Add(Msg);
  137. end;
  138. constructor TCompileThread.create(App: THTTPCompilerApplication; aItem: TCompileItem);
  139. begin
  140. FItem:=aItem;
  141. FApp:=App;
  142. FreeOnTerminate:=True;
  143. inherited create(False);
  144. end;
  145. procedure TCompileThread.Execute;
  146. Var
  147. C : TPas2JSFSCompiler;
  148. L : TStrings;
  149. begin
  150. L:=Nil;
  151. C:=TPas2JSFSCompiler.Create;
  152. Try
  153. C.ConfigSupport:=TPas2JSFileConfigSupport.Create(C);
  154. FApp.ReportBuilding(Item);
  155. L:=TStringList.Create;
  156. L.Assign(Item.Options);
  157. if (Item.ConfigFile<>'') then
  158. L.Add('@'+Item.ConfigFile);
  159. L.Add(Item.FileName);
  160. C.Log.OnLog:=@DoCompilerLog;
  161. try
  162. C.Run(ParamStr(0),Item.BaseDir,L,True);
  163. Item.FSuccess:=True;
  164. except
  165. On E : Exception do
  166. Item.Output.Add(Format('Error %s compiling %s: %s',[E.ClassName,Item.FileName,E.Message]));
  167. end;
  168. FApp.ReportBuilt(Item);
  169. Finally
  170. C.Free;
  171. L.Free;
  172. end;
  173. Item.FThread:=Nil;
  174. end;
  175. { TCompiles }
  176. function TCompiles.GetC(AIndex : Integer): TCompileItem;
  177. begin
  178. Result:=Items[Aindex] as TCompileItem;
  179. end;
  180. { TCompileItem }
  181. function TCompileItem.GetOutput: TStrings;
  182. begin
  183. If (FOutput=Nil) then
  184. FOutput:=TStringList.Create;
  185. Result:=FOutput;
  186. end;
  187. function TCompileItem.GetOptions: TStrings;
  188. begin
  189. If (FOptions=Nil) then
  190. FOptions:=TStringList.Create;
  191. Result:=FOptions;
  192. end;
  193. destructor TCompileItem.Destroy;
  194. begin
  195. FreeAndNil(FOutput);
  196. FreeAndNil(FOptions);
  197. inherited Destroy;
  198. end;
  199. { TDirWatcher }
  200. procedure TDirWatcher.DoChange(Sender: TObject; aEntry: TDirectoryEntry; AEvents: TFileEvents);
  201. begin
  202. if Assigned(FApp) then
  203. FApp.AddToStatus(AEntry,AEvents);
  204. end;
  205. constructor TDirWatcher.Create(App: THTTPCompilerApplication; ADir: String);
  206. begin
  207. Inherited create(APP);
  208. FApp:=App;
  209. FDW:=TDirwatch.Create(Self);
  210. FDW.AddWatch(ADir,allEvents);
  211. FDW.OnChange:=@DoChange;
  212. TThread.ExecuteInThread(@FDW.StartWatch);
  213. end;
  214. destructor TDirWatcher.Destroy;
  215. begin
  216. FApp:=Nil;
  217. FDW.Terminate;
  218. FreeAndNil(FDW);
  219. inherited Destroy;
  220. end;
  221. { THTTPCompilerApplication }
  222. procedure THTTPCompilerApplication.DoLog(EventType: TEventType; const Msg: String);
  223. begin
  224. {AllowWriteln}
  225. if Quiet then
  226. exit;
  227. if IsConsole then
  228. Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',EventType,'] ',Msg)
  229. else
  230. inherited DoLog(EventType, Msg);
  231. {AllowWriteln-}
  232. end;
  233. procedure THTTPCompilerApplication.Usage(Msg : String);
  234. begin
  235. {AllowWriteln}
  236. if (Msg<>'') then
  237. Writeln('Error: ',Msg);
  238. Writeln('Version ',HTTPCompilerVersion);
  239. Writeln('Usage ',ExtractFileName(ParamStr(0)),' [options] ');
  240. Writeln('Where options is one or more of : ');
  241. Writeln('-A --api=location,secret Enable location management API.');
  242. Writeln('-c --compile[=proj] Recompile project if pascal files change. Default project is app.lpr');
  243. Writeln('-d --directory=dir Base directory from which to serve files.');
  244. Writeln(' Default is current working directory: ',GetCurrentDir);
  245. Writeln('-h --help This help text');
  246. Writeln('-i --indexpage=name Directory index page to use (default: index.html)');
  247. Writeln('-I --interface=IP Listen on this interface address only.');
  248. Writeln('-m --mimetypes=file Set Filename for loading mimetypes. Default is ',GetDefaultMimeTypesFile);
  249. Writeln('-n --noindexpage Do not allow index page.');
  250. Writeln('-p --port=NNNN TCP/IP port to listen on (default is 3000)');
  251. Writeln('-q --quiet Do not write diagnostic messages');
  252. Writeln('-s --simpleserver Only serve files, do not enable compilation.');
  253. Writeln('-w --watch Watch directory for changes');
  254. Halt(Ord(Msg<>''));
  255. {AllowWriteln-}
  256. end;
  257. function THTTPCompilerApplication.GetDefaultMimeTypesFile: string;
  258. begin
  259. {$ifdef unix}
  260. Result:='/etc/mime.types';
  261. {$ifdef darwin}
  262. if not FileExists(Result) then
  263. Result:='/private/etc/apache2/mime.types';
  264. {$endif}
  265. {$else}
  266. Result:=ExtractFilePath(System.ParamStr(0))+'mime.types';
  267. {$endif}
  268. end;
  269. procedure THTTPCompilerApplication.LoadDefaultMimeTypes;
  270. begin
  271. MimeTypes.LoadKnownTypes;
  272. // To be sure
  273. MimeTypes.AddType('application/xhtml+xml','xhtml;xht');
  274. MimeTypes.AddType('text/html','html;htm');
  275. MimeTypes.AddType('text/plain','txt');
  276. MimeTypes.AddType('application/javascript','js');
  277. MimeTypes.AddType('text/plain','map');
  278. MimeTypes.AddType('application/json','json');
  279. MimeTypes.AddType('image/png','png');
  280. MimeTypes.AddType('image/jpeg','jpeg;jpg');
  281. MimeTypes.AddType('image/gif','gif');
  282. MimeTypes.AddType('image/jp2','jp2');
  283. MimeTypes.AddType('image/tiff','tiff;tif');
  284. MimeTypes.AddType('application/pdf','pdf');
  285. MimeTypes.AddType('text/css','css');
  286. end;
  287. constructor THTTPCompilerApplication.Create(AOWner: TComponent);
  288. begin
  289. inherited Create(AOWner);
  290. FStatusLock:=TCriticalSection.Create;
  291. FStatusList:=TFPObjectList.Create(False);
  292. FCompiles:=TCompiles.Create(TCompileItem);
  293. end;
  294. destructor THTTPCompilerApplication.Destroy;
  295. begin
  296. FStatusLock.Enter;
  297. try
  298. FreeAndNil(FCompiles);
  299. FreeAndNil(FStatusList);
  300. finally
  301. FStatusLock.Leave;
  302. end;
  303. FreeAndNil(FStatusLock);
  304. inherited Destroy;
  305. end;
  306. procedure THTTPCompilerApplication.StartWatch(ADir : String);
  307. begin
  308. FDW:=TDirWatcher.Create(Self,ADir);
  309. end;
  310. procedure THTTPCompilerApplication.ReportBuilding(AItem: TCompileItem);
  311. Var
  312. O : TJSONObject;
  313. begin
  314. O:=TJSONObject.Create(['action','building','compileID',AItem.ID,'project',AItem.FileName,'config',AItem.ConfigFile]);
  315. AddToStatus(O);
  316. end;
  317. procedure THTTPCompilerApplication.ReportBuilt(AItem: TCompileItem);
  318. Var
  319. O : TJSONObject;
  320. A : TJSONArray;
  321. I : Integer;
  322. begin
  323. A:=TJSONArray.Create;
  324. For I:=0 to AItem.Output.Count-1 do
  325. A.Add(AItem.Output[i]);
  326. O:=TJSONObject.Create(['action','built','compileID',AItem.ID,'project',AItem.FileName,'config',AItem.ConfigFile,'output',A,'success',AItem.Success]);
  327. AddToStatus(O);
  328. end;
  329. procedure THTTPCompilerApplication.AddToStatus(O : TJSONObject);
  330. begin
  331. FStatusLock.Enter;
  332. try
  333. {$ifdef VerboseHTTPCompiler}
  334. Writeln('Adding to status ',Assigned(O),' : ',O.ClassName);
  335. {$endif}
  336. FStatusList.Add(O);
  337. finally
  338. FStatusLock.Leave;
  339. end;
  340. end;
  341. procedure THTTPCompilerApplication.AddToStatus(AEntry: TDirectoryEntry; AEvents: TFileEvents);
  342. Var
  343. O : TJSONObject;
  344. FN : String;
  345. begin
  346. Log(etDebug,'File change detected: %s (%s)',[AEntry.name,FileEventsToStr(AEvents)]);
  347. O:=TJSONObject.Create(['action','file','name',AEntry.name,'events',FileEventsToStr(AEvents)]);
  348. if Pos(ExtractFileExt(AEntry.Name),'.lpr.pas.pp.inc.dpr')>0 then
  349. FN:=AEntry.Name;
  350. if (FN<>'') then
  351. O.Add('recompile',true);
  352. AddToStatus(O);
  353. if (FN<>'') then
  354. begin
  355. Log(etDebug,'File change forces recompile: %s',[AEntry.name]);
  356. ScheduleCompile('',Nil);
  357. end;
  358. end;
  359. procedure THTTPCompilerApplication.DoStatusRequest(ARequest : TRequest; AResponse : TResponse);
  360. Var
  361. R,O : TJSONObject;
  362. A : TJSONArray;
  363. I : integer;
  364. begin
  365. Log(etDebug,'Status request from: %s',[ARequest.RemoteAddress]);
  366. R:=Nil;
  367. try
  368. FStatusLock.Enter;
  369. try
  370. if (FStatusList.Count=0) then
  371. R:=TJSONObject.Create(['ping',True])
  372. else
  373. begin
  374. {$ifdef VerboseHTTPCompiler}
  375. Writeln(FStatusList[0].ClassName);
  376. {$endif}
  377. O:=FStatusList[0] as TJSONObject;
  378. FStatusList.Delete(0);
  379. if O.Get('action','')<>'file' then
  380. R:=O
  381. else
  382. begin
  383. // If first event is file event, then add and delete all file events in list.
  384. A:=TJSONArray.Create([O]);
  385. O.Delete('action');
  386. R:=TJSONObject.Create(['action','sync','files',A]);
  387. For I:=FStatusList.Count-1 downto 0 do
  388. begin
  389. O:=FStatusList[I] as TJSONObject;
  390. if (O.Get('action','')='file') then
  391. begin
  392. A.Add(O);
  393. O.Delete('action');
  394. FStatusList.Delete(I);
  395. end;
  396. end;
  397. end
  398. end;
  399. finally
  400. FStatusLock.Leave;
  401. end;
  402. AResponse.ContentType:='application/json';
  403. AResponse.Content:=R.AsJSON;
  404. AResponse.SendResponse;
  405. finally
  406. R.Free;
  407. end;
  408. end;
  409. function THTTPCompilerApplication.ScheduleCompile(const aProjectFile: String;
  410. Options: TStrings): Integer;
  411. Var
  412. CI : TCompileItem;
  413. I,TC : Integer;
  414. begin
  415. TC:=0;
  416. For I:=0 to FCompiles.Count-1 do
  417. if Assigned(FCompiles[I].THread) then
  418. Inc(TC);
  419. if TC>10 then
  420. begin
  421. Log(etError,'Refusing compile of file "%s" using config file "%s"',[AProjectFile, ConfigFile]);
  422. Exit(nErrTooManyThreads);
  423. end;
  424. CI:=FCompiles.Add as TCompileItem;
  425. Log(etInfo,'Scheduling compile ID %d of file "%s" using config file "%s"',[CI.ID,AProjectFile, ConfigFile]);
  426. CI.BaseDir:=BaseDir;
  427. CI.FileName:=AProjectFile;
  428. CI.ConfigFile:=ConfigFile;
  429. if Assigned(Options) then
  430. CI.Options.Assign(Options);
  431. TCompileThread.Create(Self,CI);
  432. Result:=CI.ID;
  433. end;
  434. procedure THTTPCompilerApplication.DoRecompile(ARequest: TRequest; AResponse: TResponse);
  435. Var
  436. ID : Integer;
  437. PF,CL : String;
  438. Options: TStrings;
  439. begin
  440. PF:=ARequest.ContentFields.Values['ProjectFile'];
  441. CL:=ARequest.ContentFields.Values['CompileOptions'];
  442. if PF='' then
  443. PF:=ProjectFile;
  444. If (PF='') then
  445. begin
  446. AResponse.ContentType:='application/json';
  447. AResponse.Content:='{ "success" : false, "message": "no project file set or provided" }';
  448. AResponse.Code:=404;
  449. AResponse.CodeText:='No project file';
  450. end
  451. else
  452. begin
  453. Options:=Nil;
  454. try
  455. if CL<>'' then
  456. begin
  457. Options:=TStringList.Create;
  458. Options.Text:=Cl;
  459. end;
  460. ID:=ScheduleCompile(PF,Options);
  461. finally
  462. FreeAndNil(Options);
  463. end;
  464. if ID=nErrTooManyThreads then
  465. begin
  466. AResponse.Code:=403;
  467. AResponse.CodeText:='Too many compiles';
  468. AResponse.ContentType:='application/json';
  469. AResponse.Content:='{ "success" : false, "message": "Too many compiles running" }';
  470. end
  471. else
  472. begin
  473. AResponse.Code:=200;
  474. AResponse.ContentType:='application/json';
  475. AResponse.Content:=Format('{ "success" : true, "file": "%s", "commandLine" : "%s", "compileID": %d }',[StringToJSONString(PF),StringToJSONString(CL),ID]);
  476. end
  477. end;
  478. AResponse.SendResponse;
  479. end;
  480. function THTTPCompilerApplication.HandleCompileOptions(aDir: String): Boolean;
  481. begin
  482. Result:=False;
  483. Watch:=HasOption('w','watch');
  484. if Hasoption('P','project') then
  485. begin
  486. ProjectFile:=GetOptionValue('P','project');
  487. if ProjectFile='' then
  488. ProjectFile:=IncludeTrailingPathDelimiter(aDir)+'app.lpr';
  489. If Not FileExists(ProjectFile) then
  490. begin
  491. Terminate;
  492. Log(etError,'Project file "%s" does not exist, aborting.',[ProjectFile]);
  493. Exit;
  494. end;
  495. ConfigFile:=GetOptionValue('c','config');
  496. if (ConfigFile='') then
  497. ConfigFile:=ChangeFileExt(Projectfile,'.cfg');
  498. if not FileExists(ConfigFile) then
  499. ConfigFile:='';
  500. end;
  501. if Watch then
  502. begin
  503. if (ProjectFile='') then
  504. Log(etWarning,'No project file specified, disabling watch.') ;
  505. StartWatch(aDir);
  506. end;
  507. Result:=True;
  508. end;
  509. procedure THTTPCompilerApplication.DoProxyLog(Sender: TObject; const aMethod, aLocation, aFromURL, aToURL: String);
  510. Var
  511. Msg : String;
  512. begin
  513. if Quiet then
  514. exit;
  515. Msg:=Format('(Proxy redirect) location: %s, Method: %s, From: %s, to: %s',[aLocation,aMethod,aFromURl,atoURL]);
  516. if IsConsole then
  517. {AllowWriteln}
  518. Writeln(FormatDateTime('yyyy-mm-dd hh:nn:ss.zzz',Now),' [',etInfo,'] ',Msg)
  519. {AllowWriteln-}
  520. else
  521. inherited DoLog(etInfo, Msg);
  522. end;
  523. procedure THTTPCompilerApplication.DoEcho(ARequest: TRequest; AResponse: TResponse);
  524. Var
  525. L : TStrings;
  526. begin
  527. L:=TStringList.Create;
  528. try
  529. L.AddStrings(['<!doctype html>',
  530. '<html>',
  531. '<head>',
  532. '<title>Echo request</title>',
  533. '</head>',
  534. '<body>'
  535. ]);
  536. DumpRequest(aRequest,L);
  537. L.AddStrings(['</body>','</html>']);
  538. AResponse.Content:=L.Text;
  539. AResponse.SendResponse;
  540. finally
  541. L.Free;
  542. end;
  543. end;
  544. procedure THTTPCompilerApplication.Doquit(ARequest: TRequest; AResponse: TResponse);
  545. Var
  546. PWD : String;
  547. begin
  548. PWD:=ARequest.QueryFields.Values['password'];
  549. if PWD='' then
  550. ARequest.ContentFields.Values['password'];
  551. if PWD=FPassword then
  552. begin
  553. AResponse.Content:='OK';
  554. AResponse.SendContent;
  555. Terminate;
  556. end
  557. else
  558. begin
  559. AResponse.Code:=403;
  560. AResponse.CodeText:='Forbidden';
  561. AResponse.SendContent;
  562. end;
  563. end;
  564. procedure THTTPCompilerApplication.ReadConfigFile(Const ConfigFile : string);
  565. Const
  566. SConfig = 'Server';
  567. SProxy = 'Proxy';
  568. SLocations = 'Locations';
  569. KeyPort = 'Port';
  570. KeyInterface = 'Interface';
  571. KeyDir = 'Directory';
  572. KeyIndexPage = 'IndexPage';
  573. KeyHostName = 'hostname';
  574. keyMimetypes = 'mimetypes';
  575. KeySSL = 'SSL';
  576. KeyQuiet = 'quiet';
  577. KeyQuit = 'quit';
  578. KeyEcho = 'echo';
  579. KeyNoIndexPage = 'noindexpage';
  580. KeyBackground = 'background';
  581. KeyMaxAge = 'MaxAge';
  582. KeyAPI = 'API';
  583. Var
  584. L : TStringList;
  585. P,U : String;
  586. I : Integer;
  587. begin
  588. if (ConfigFile='') or Not FileExists(ConfigFile) then exit;
  589. L:=Nil;
  590. With TMemIniFile.Create(ConfigFile) do
  591. try
  592. FBaseDir:=ReadString(SConfig,KeyDir,BaseDir);
  593. Port:=ReadInteger(SConfig,KeyPort,Port);
  594. InterfaceAddress:=ReadString(SConfig,KeyInterface,InterfaceAddress);
  595. Quiet:=ReadBool(SConfig,KeyQuiet,Quiet);
  596. FMimeFile:=ReadString(SConfig,keyMimetypes,MimeFile);
  597. NoIndexPage:=ReadBool(SConfig,KeyNoIndexPage,NoIndexPage);
  598. IndexPageName:=ReadString(SConfig,KeyIndexPage,IndexPageName);
  599. HostName:=ReadString(SConfig,KeyHostName,HostName);
  600. UseSSL:=ReadBool(SConfig,KeySSL,UseSSL);
  601. FBackground:=ReadBool(SConfig,Keybackground,FBackGround);
  602. FPassword:=ReadString(SConfig,KeyQuit,FPassword);
  603. FEcho:=ReadBool(SConfig,KeyEcho,FEcho);
  604. FMaxAge:=ReadInteger(SConfig,KeyMaxAge,FMaxAge);
  605. FAPI:=ReadString(SConfig,keyAPI,'');
  606. L:=TstringList.Create;
  607. ReadSectionValues(SProxy,L,[]);
  608. For I:=0 to L.Count-1 do
  609. begin
  610. L.GetNameValue(I,P,U);
  611. if (P<>'') and (U<>'') then
  612. ProxyManager.RegisterLocation(P,U).AppendPathInfo:=true;
  613. end;
  614. L.Clear;
  615. ReadSectionValues(SLocations,L,[]);
  616. For I:=0 to L.Count-1 do
  617. begin
  618. L.GetNameValue(I,P,U);
  619. if (P<>'') and (U<>'') then
  620. RegisterFileLocation(P,U);
  621. end;
  622. finally
  623. L.Free;
  624. Free;
  625. end;
  626. end;
  627. function THTTPCompilerApplication.ProcessOptions: Boolean;
  628. Var
  629. IndexPage,D : String;
  630. begin
  631. Result:=False;
  632. if HasOption('A','api') then
  633. FAPI:=GetOptionValue('A','api');
  634. FServeOnly:=FServeOnly or HasOption('s','serve-only');
  635. Quiet:=Quiet or HasOption('q','quiet');
  636. if (Port=0) or HasOption('p','port') then
  637. Port:=StrToIntDef(GetOptionValue('p','port'),3000);
  638. if HasOption('d','directory') then
  639. D:=GetOptionValue('d','directory');
  640. if D='' then
  641. D:=GetCurrentDir;
  642. if HasOption('m','mimetypes') then
  643. MimeTypesFile:=GetOptionValue('m','mimetypes');
  644. if MimeTypesFile='' then
  645. begin
  646. MimeTypesFile:=GetDefaultMimeTypesFile;
  647. if not FileExists(MimeTypesFile) then
  648. begin
  649. MimeTypesFile:='';
  650. LoadDefaultMimeTypes;
  651. end;
  652. end
  653. else if not FileExists(MimeTypesFile) then
  654. Log(etWarning,'mimetypes file not found: '+MimeTypesFile);
  655. FBaseDir:=D;
  656. if not ServeOnly then
  657. if not HandleCompileOptions(D) then
  658. exit(False);
  659. TSimpleFileModule.BaseDir:=IncludeTrailingPathDelimiter(D);
  660. TSimpleFileModule.OnLog:=@Log;
  661. Log(etInfo,'Listening on port %d, serving files from directory: %s',[Port,D]);
  662. if ServeOnly then
  663. Log(etInfo,'Compile requests will be ignored.');
  664. NoIndexPage:=NoIndexPage or HasOption('n','noindexpage');
  665. if HasOption('i','indexpage') then
  666. IndexPage:=GetOptionValue('i','indexpage');
  667. if HasOption('I','interface') then
  668. InterfaceAddress:=GetOptionValue('I','interface');
  669. If not NoIndexPage then
  670. begin
  671. if (IndexPage='') then
  672. IndexPage:='index.html';
  673. Log(etInfo,'Using index page %s',[IndexPage]);
  674. TSimpleFileModule.IndexPageName:=IndexPage;
  675. end;
  676. Result:=True;
  677. end;
  678. procedure THTTPCompilerApplication.DoRun;
  679. Var
  680. S : String;
  681. begin
  682. S:=Checkoptions('shqVd:ni:p:wP::cm:A:I:',['help','quiet','version','noindexpage','directory:','port:','indexpage:','watch','project::','config:','simpleserver','mimetypes:','api:','interface:']);
  683. if (S<>'') or HasOption('h','help') then
  684. Usage(S);
  685. if HasOption('V','version') then
  686. begin
  687. {AllowWriteln}
  688. writeln(HTTPCompilerVersion);
  689. {AllowWriteln-}
  690. Terminate;
  691. exit;
  692. end;
  693. if HasOption('c','config') then
  694. ConfigFile:=GetOptionValue('c','config')
  695. else
  696. ConfigFile:='compileserver.ini';
  697. Port:=3000;
  698. ReadConfigFile(ConfigFile);
  699. If not ProcessOptions then
  700. begin
  701. Terminate;
  702. exit;
  703. end;
  704. if FBackground then
  705. begin
  706. {$ifdef unix}
  707. if FPFork>0 then Halt(0);
  708. {$else}
  709. Log(etError,'Background option not supported');
  710. {$endif}
  711. end;
  712. // Handle options
  713. if FPassword<>'' then
  714. HTTPRouter.RegisterRoute('/quit',rmAll,@Doquit,False);
  715. if FEcho then
  716. HTTPRouter.RegisterRoute('/echo',rmAll,@DoEcho,False);
  717. if ProxyManager.LocationCount>0 then
  718. begin
  719. TProxyWebModule.RegisterModule('Proxy',True);
  720. ProxyManager.OnLog:=@DoProxyLog;
  721. end;
  722. DefaultCacheControlMaxAge:=FMaxAge; // one year by default
  723. if not ServeOnly then
  724. begin
  725. httprouter.RegisterRoute('$sys/compile',rmPost,@DoRecompile);
  726. httprouter.RegisterRoute('$sys/status',rmGet,@DoStatusRequest);
  727. end;
  728. if FAPI<>'' then
  729. {$IF FPC_FULLVERSION > 30300}
  730. TFPWebFileLocationAPIModule.RegisterFileLocationAPI(ExtractWord(1,FAPI,[',']),ExtractWord(2,FAPI,[',']));
  731. {$ELSE}
  732. Log(etError,'API support missing, Compile with fpc 3.3.1+');
  733. {$ENDIF}
  734. TSimpleFileModule.RegisterDefaultRoute;
  735. if InterfaceAddress<>'' then
  736. HTTPHandler.Address:=InterfaceAddress;
  737. try
  738. inherited DoRun;
  739. except
  740. on E: ESocketError do begin
  741. Log(etError,E.ClassName+': '+E.Message);
  742. ExitCode:=nExitCodeSocketError;
  743. Terminate;
  744. end;
  745. end;
  746. end;
  747. end.