* Show info in a more structured way

* Allow to compile with FPC 3.2.2
* Fix --coi option.
* Allow to specify custom response headers in config file [Headers] section
This commit is contained in:
Michaël Van Canneyt 2024-11-01 11:41:58 +01:00
parent 18e519963f
commit dfb4015067

View File

@ -42,8 +42,11 @@ uses
{$ifdef unix} {$ifdef unix}
baseunix, baseunix,
{$endif} {$endif}
{$IFNDEF VER3_2}
fpdebugcapturesvc,
{$ENDIF}
sysutils, Classes, jsonparser, fpjson, strutils, inifiles, sslbase, httproute, httpdefs, fpmimetypes, fpwebfile, fpwebproxy, sysutils, Classes, jsonparser, fpjson, strutils, inifiles, sslbase, httproute, httpdefs, fpmimetypes, fpwebfile, fpwebproxy,
webutil, fpdebugcapturesvc; webutil;
Const Const
ServerVersion = '1.0'; ServerVersion = '1.0';
@ -57,15 +60,20 @@ Type
TParentApp = TCustomHTTPApplication; TParentApp = TCustomHTTPApplication;
{$ENDIF} {$ENDIF}
{$IFDEF VER3_2}
{ TMySimpleFileModule } { TMySimpleFileModule }
TMySimpleFileModule = class(TFPCustomFileModule)
TMySimpleFileModule = class(TSimpleFileModule)
Public Public
Constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override;
Procedure SendFile(const AFileName: String; AResponse: TResponse); override; Procedure SendFile(const AFileName: String; AResponse: TResponse); override;
end; end;
{$ENDIF}
THTTPApplication = Class(TParentApp) THTTPApplication = Class(TParentApp)
private private
FProxyDefs : TStrings;
FLocations : TStrings;
FHeaders: TStrings;
FAPISecret : String; FAPISecret : String;
FBaseDir: string; FBaseDir: string;
FIndexPageName: String; FIndexPageName: String;
@ -79,16 +87,23 @@ Type
FMaxAge : Integer; FMaxAge : Integer;
FCrossOriginIsolation : Boolean; FCrossOriginIsolation : Boolean;
procedure AddProxy(const aProxyDef: String); procedure AddProxy(const aProxyDef: String);
procedure ApplyCoi(Sender: TObject; aResponse: TResponse);
procedure DoEcho(ARequest: TRequest; AResponse: TResponse); procedure DoEcho(ARequest: TRequest; AResponse: TResponse);
procedure DoProxyLog(Sender: TObject; const aMethod, aLocation, aFromURL, aToURL: String); procedure DoProxyLog(Sender: TObject; const aMethod, aLocation, aFromURL, aToURL: String);
procedure DoQuit(ARequest: TRequest; AResponse: TResponse); procedure DoQuit(ARequest: TRequest; AResponse: TResponse);
procedure LoadMimeTypes; procedure LoadMimeTypes;
procedure ProcessOptions; procedure ProcessOptions;
procedure ReadConfigFile(const ConfigFile: string); procedure ReadConfigFile(const ConfigFile: string);
{$IFNDEF VER3_2}
procedure SetupCapture; procedure SetupCapture;
Procedure RegisterCustomHeaders;
{$ENDIF}
procedure Usage(Msg: String); procedure Usage(Msg: String);
procedure Writeinfo; procedure Writeinfo;
procedure RegisterFileLocations;
Procedure RegisterProxies;
Public Public
constructor create(aOwner : TComponent); override;
Destructor Destroy; override; Destructor Destroy; override;
published published
procedure DoLog(EventType: TEventType; const Msg: String); override; procedure DoLog(EventType: TEventType; const Msg: String); override;
@ -104,16 +119,38 @@ Type
Var Var
Application : THTTPApplication; Application : THTTPApplication;
{$IFDEF VER3_2}
{ TMySimpleFileModule } { TMySimpleFileModule }
constructor TMySimpleFileModule.CreateNew(AOwner: TComponent; CreateMode: Integer);
begin
inherited CreateNew(AOwner, CreateMode);
end;
procedure TMySimpleFileModule.SendFile(const AFileName: String; AResponse: TResponse); procedure TMySimpleFileModule.SendFile(const AFileName: String; AResponse: TResponse);
begin begin
AResponse.SetCustomHeader('Cross-Origin-Embedder-Policy','require-corp'); AResponse.SetCustomHeader('Cross-Origin-Embedder-Policy','require-corp');
AResponse.SetCustomHeader('Cross-Origin-Opener-Policy','same-origin'); AResponse.SetCustomHeader('Cross-Origin-Opener-Policy','same-origin');
inherited SendFile(AFileName, AResponse); inherited SendFile(AFileName, AResponse);
end; end;
{$ENDIF}
{ THTTPApplication } { THTTPApplication }
constructor THTTPApplication.create(aOwner : TComponent);
begin
Inherited;
FProxyDefs:=TStringList.Create;
FLocations:=TStringList.Create;
FHeaders:=TStringList.Create;
end;
procedure THTTPApplication.ApplyCoi(Sender : TObject; aResponse : TResponse);
begin
AResponse.SetCustomHeader('Cross-Origin-Embedder-Policy','require-corp');
AResponse.SetCustomHeader('Cross-Origin-Opener-Policy','same-origin');
end;
procedure THTTPApplication.DoEcho(ARequest: TRequest; AResponse: TResponse); procedure THTTPApplication.DoEcho(ARequest: TRequest; AResponse: TResponse);
@ -141,7 +178,7 @@ begin
end; end;
procedure THTTPApplication.Doquit(ARequest: TRequest; AResponse: TResponse); procedure THTTPApplication.DoQuit(ARequest: TRequest; AResponse: TResponse);
Var Var
PWD : String; PWD : String;
@ -217,8 +254,10 @@ begin
Writeln('-q --quiet Do not write diagnostic messages.'); Writeln('-q --quiet Do not write diagnostic messages.');
Writeln('-Q --quit=PWD Register /quit URL. Send request with password variable equal to PWD to stop.'); Writeln('-Q --quit=PWD Register /quit URL. Send request with password variable equal to PWD to stop.');
Writeln('-s --ssl Use SSL.'); Writeln('-s --ssl Use SSL.');
{$IFNDEF VER3_2}
Writeln('-u --capture[=FILE] Set up /debugcapture route to capture output sent by browser.'); Writeln('-u --capture[=FILE] Set up /debugcapture route to capture output sent by browser.');
Writeln(' If FILE is specified, write to file. If not specified, writes to STDOUT.'); Writeln(' If FILE is specified, write to file. If not specified, writes to STDOUT.');
{$ENDIF}
Writeln('-V --version Display server version and exit.'); Writeln('-V --version Display server version and exit.');
Writeln('-x --proxy=proxydef Add proxy definition. Definition is of form:'); Writeln('-x --proxy=proxydef Add proxy definition. Definition is of form:');
Writeln(' name:BaseURL'); Writeln(' name:BaseURL');
@ -268,6 +307,7 @@ Const
SConfig = 'Server'; SConfig = 'Server';
SProxy = 'Proxy'; SProxy = 'Proxy';
SLocations = 'Locations'; SLocations = 'Locations';
SHeaders = 'Headers';
KeyPort = 'Port'; KeyPort = 'Port';
KeyInterface = 'Interface'; KeyInterface = 'Interface';
@ -288,16 +328,8 @@ Const
procedure THTTPApplication.ReadConfigFile(const ConfigFile: string); procedure THTTPApplication.ReadConfigFile(const ConfigFile: string);
Var
L : TStringList;
P,U : String;
I : Integer;
begin begin
if (ConfigFile='') or Not FileExists(ConfigFile) then exit; if (ConfigFile='') or Not FileExists(ConfigFile) then exit;
L:=Nil;
With TMemIniFile.Create(ConfigFile) do With TMemIniFile.Create(ConfigFile) do
try try
BaseDir:=ReadString(SConfig,KeyDir,BaseDir); BaseDir:=ReadString(SConfig,KeyDir,BaseDir);
@ -315,32 +347,84 @@ begin
FMaxAge:=ReadInteger(SConfig,KeyMaxAge,FMaxAge); FMaxAge:=ReadInteger(SConfig,KeyMaxAge,FMaxAge);
FAPISecret:=ReadString(SConfig,KeyAPI,''); FAPISecret:=ReadString(SConfig,KeyAPI,'');
FCrossOriginIsolation:=ReadBool(SConfig,KeyCOI,FCrossOriginIsolation); FCrossOriginIsolation:=ReadBool(SConfig,KeyCOI,FCrossOriginIsolation);
{$IFNDEF VER3_2}
if ValueExists(SConfig,KeyCapture) then if ValueExists(SConfig,KeyCapture) then
begin begin
TDebugCaptureService.Instance.LogFileName:=ReadString(SConfig,keyCapture,''); TDebugCaptureService.Instance.LogFileName:=ReadString(SConfig,keyCapture,'');
end; end;
L:=TstringList.Create; {$ENDIF}
ReadSectionValues(SProxy,L,[]); ReadSectionValues(SProxy,FProxyDefs,[]);
For I:=0 to L.Count-1 do ReadSectionValues(SLocations,FLocations,[]);
begin ReadSectionValues(SHeaders,FHeaders,[]);
L.GetNameValue(I,P,U);
if (P<>'') and (U<>'') then
ProxyManager.RegisterLocation(P,U).AppendPathInfo:=true;
end;
L.Clear;
ReadSectionValues(SLocations,L,[]);
For I:=0 to L.Count-1 do
begin
L.GetNameValue(I,P,U);
if (P<>'') and (U<>'') then
RegisterFileLocation(P,U);
end;
finally finally
L.Free;
Free; Free;
end; end;
end; end;
procedure THTTPApplication.RegisterProxies;
var
I : integer;
Proxy,URL : String;
begin
For I:=0 to FProxyDefs.Count-1 do
begin
FProxyDefs.GetNameValue(I,Proxy,Url);
if (Proxy<>'') and (Url<>'') then
ProxyManager.RegisterLocation(Proxy,Url).AppendPathInfo:=true;
end;
end;
{$IFNDEF VER3_2}
procedure THTTPApplication.RegisterCustomHeaders;
var
I : integer;
lName,lValue : String;
begin
For I:=0 to FLocations.Count-1 do
begin
FLocations.GetNameValue(I,lName,lValue);
if (lName<>'') and (lValue<>'') then
TFPCustomFileModule.RegisterGlobalResponseHeader(lName,lValue);
end;
end;
procedure THTTPApplication.SetupCapture;
Var
Dest : String;
Svc : TDebugCaptureService;
begin
Svc:=TDebugCaptureService.Instance;
Dest:=Svc.LogFileName;
if (Dest='') and Svc.LogToConsole then
Dest:='Console';
if Dest<>'' then
begin
DoLog(etInfo,Format('Setting up capture on route "%s", writing to: %s',[SCaptureRoute,Dest]));
HTTPRouter.RegisterRoute(SCaptureRoute,rmPost,@Svc.HandleRequest,False);
end;
end;
{$ENDIF}
procedure THTTPApplication.RegisterFileLocations;
var
I : integer;
loc,Dir : String;
begin
For I:=0 to FLocations.Count-1 do
begin
FLocations.GetNameValue(I,Loc,Dir);
if (Loc<>'') and (Dir<>'') then
RegisterFileLocation(Loc,Dir);
end;
end;
procedure THTTPApplication.ProcessOptions; procedure THTTPApplication.ProcessOptions;
procedure HasGetOptionValue(var aValue: string; Const C: Char; Const S : String); procedure HasGetOptionValue(var aValue: string; Const C: Char; Const S : String);
@ -381,6 +465,7 @@ begin
FBackground:=true; FBackground:=true;
if hasOption('o','coi') then if hasOption('o','coi') then
FCrossOriginIsolation:=true; FCrossOriginIsolation:=true;
{$IFNDEF VER3_2}
if HasOption('u','capture') then if HasOption('u','capture') then
begin begin
S:=GetOptionValue('u','capture'); S:=GetOptionValue('u','capture');
@ -389,27 +474,47 @@ begin
else else
TDebugCaptureService.Instance.LogFileName:=S; TDebugCaptureService.Instance.LogFileName:=S;
end; end;
{$ENDIF}
end; end;
procedure THTTPApplication.Writeinfo; procedure THTTPApplication.Writeinfo;
function BtoS(B : Boolean) : string;
begin
Result:=BoolToStr(B,'True','False');
end;
Var Var
I : Integer; I : Integer;
Base,N,V : String;
begin begin
Log(etInfo,'Listening on port %d, serving files from directory: %s (using SSL: %s).',[Port,BaseDir,BoolToStr(UseSSL,'true','false')]); Log(etInfo,'Listening on port %d',[Port]);
Log(etInfo,'Serving files from directory: %s',[BaseDir]);
For I:=0 to ProxyManager.LocationCount-1 do For I:=0 to ProxyManager.LocationCount-1 do
with ProxyManager.Locations[i] do with ProxyManager.Locations[i] do
Log(etInfo,'Proxy location /proxy/%s redirects to: %s',[Path,URL]); Log(etInfo,'Proxy location /proxy/%s redirects to: %s',[Path,URL]);
For I:=0 to FLocations.Count-1 do
begin
FLocations.GetNameValue(I,N,V);
Log(etInfo,'Enabled file location "%s", serving from: %s',[N,V]);
end;
Log(etInfo,'Enabled index page: %s',[BToS(NoIndexPage)]);
if not NoIndexPage then if not NoIndexPage then
Log(etInfo,'Using index page: %s',[IndexPageName]); Log(etInfo,'Index page name: %s',[IndexPageName]);
if (Self.FPassword<>'') then Log(etInfo,'Enabled SSL: %s',[BtoS(UseSSL)]);
DoLog(etInfo,'/quit route set up.'); Log(etInfo,'Enabled COI/CORP: %s',[BToS(FCrossOriginIsolation)]);
if FEcho then Log(etInfo,'Enabled /quit route: %s',[BtoS(Self.FPassword<>'')]);
DoLog(etInfo,'Setting up /echo route.'); Log(etInfo,'Enabled /echo route: %s',[BtoS(FEcho)]);
Log(etInfo,'Location REST API '+IfThen(FAPISecret<>'','','NOT ')+'activated.'); Log(etInfo,'Enabled location REST API: %s',[BtoS(FAPISecret<>'')]);
Log(etInfo,'Navigate to: http'+IfThen(UseSSL,'s','')+'://localhost:'+IntToStr(Port)+'/'); Base:='http'+IfThen(UseSSL,'s','')+'://localhost:'+IntToStr(Port)+'/';
Log(etInfo,'Navigate to: %s',[Base]);
For I:=0 to FLocations.Count-1 do
begin
FLocations.GetNameValue(I,N,V);
Log(etInfo,'Navigate to location "%s" at: %s/',[N,Base+N]);
end;
end; end;
destructor THTTPApplication.Destroy; destructor THTTPApplication.Destroy;
@ -417,24 +522,6 @@ begin
inherited Destroy; inherited Destroy;
end; end;
procedure THTTPApplication.SetupCapture;
Var
Dest : String;
Svc : TDebugCaptureService;
begin
Svc:=TDebugCaptureService.Instance;
Dest:=Svc.LogFileName;
if (Dest='') and Svc.LogToConsole then
Dest:='Console';
if Dest<>'' then
begin
DoLog(etInfo,Format('Setting up capture on route "%s", writing to: %s',[SCaptureRoute,Dest]));
HTTPRouter.RegisterRoute(SCaptureRoute,rmPost,@Svc.HandleRequest,False);
end;
end;
procedure THTTPApplication.DoRun; procedure THTTPApplication.DoRun;
Var Var
@ -465,7 +552,9 @@ begin
Log(etError,'Background option not supported.'); Log(etError,'Background option not supported.');
{$endif} {$endif}
end; end;
{$IFNDEF VER3_2}
SetupCapture; SetupCapture;
{$ENDIF}
if FPassword<>'' then if FPassword<>'' then
begin begin
HTTPRouter.RegisterRoute('/quit',rmAll,@Doquit,False); HTTPRouter.RegisterRoute('/quit',rmAll,@Doquit,False);
@ -482,10 +571,23 @@ begin
BaseDir:=GetCurrentDir; BaseDir:=GetCurrentDir;
if (BaseDir<>'') then if (BaseDir<>'') then
BaseDir:=IncludeTrailingPathDelimiter(BaseDir); BaseDir:=IncludeTrailingPathDelimiter(BaseDir);
{$IFNDEF VER3_2_2}
if FAPISecret<>'' then if FAPISecret<>'' then
TFPWebFileLocationAPIModule.RegisterFileLocationAPI(ExtractWord(1,FAPISecret,[',']),ExtractWord(2,FAPISecret,[','])); TFPWebFileLocationAPIModule.RegisterFileLocationAPI(ExtractWord(1,FAPISecret,[',']),ExtractWord(2,FAPISecret,[',']));
{$ENDIF}
if FCrossOriginIsolation then if FCrossOriginIsolation then
TSimpleFileModule.DefaultSimpleFileModuleClass:=TMySimpleFileModule; begin
{$IFDEF VER3_2_2}
DefaultFileModuleClass:=TMySimpleFileModule;
{$ELSE}
TFPCustomFileModule.OnPrepareResponse:=@ApplyCoi;
{$ENDIF}
end;
RegisterProxies;
RegisterFileLocations;
{$IFNDEF VER_3_2}
RegisterCustomHeaders;
{$ENDIF}
TSimpleFileModule.RegisterDefaultRoute; TSimpleFileModule.RegisterDefaultRoute;
TSimpleFileModule.BaseDir:=BaseDir; TSimpleFileModule.BaseDir:=BaseDir;
TSimpleFileModule.OnLog:=@Log; TSimpleFileModule.OnLog:=@Log;