httpcompiler.pp 22 KB

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