2
0

httpcompiler.pp 25 KB

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