|
@@ -44,7 +44,7 @@ uses
|
|
{$ifdef unix}
|
|
{$ifdef unix}
|
|
baseunix,
|
|
baseunix,
|
|
{$endif}
|
|
{$endif}
|
|
- sysutils,Classes, jsonparser, strutils, inifiles, sslbase, httproute, httpdefs, fpmimetypes, fpwebfile, fpwebproxy, webutil;
|
|
|
|
|
|
+ sysutils,Classes, jsonparser, fpjson, strutils, inifiles, sslbase, httproute, httpdefs, fpmimetypes, fpwebfile, fpwebproxy, webutil;
|
|
|
|
|
|
Type
|
|
Type
|
|
|
|
|
|
@@ -64,6 +64,8 @@ Type
|
|
|
|
|
|
THTTPApplication = Class(TParentApp)
|
|
THTTPApplication = Class(TParentApp)
|
|
private
|
|
private
|
|
|
|
+ FCaptureFileName : String;
|
|
|
|
+ FCaptureStream : TFileStream;
|
|
FAPISecret : String;
|
|
FAPISecret : String;
|
|
FBaseDir: string;
|
|
FBaseDir: string;
|
|
FIndexPageName: String;
|
|
FIndexPageName: String;
|
|
@@ -78,13 +80,20 @@ Type
|
|
FCrossOriginIsolation : Boolean;
|
|
FCrossOriginIsolation : Boolean;
|
|
procedure AddProxy(const aProxyDef: String);
|
|
procedure AddProxy(const aProxyDef: String);
|
|
procedure DoEcho(ARequest: TRequest; AResponse: TResponse);
|
|
procedure DoEcho(ARequest: TRequest; AResponse: TResponse);
|
|
|
|
+ procedure DoCapture(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);
|
|
|
|
+ function GetCaptureJSON(ARequest: TRequest; AResponse: TResponse;
|
|
|
|
+ var aJSON: TJSONData): TJSONArray;
|
|
procedure LoadMimeTypes;
|
|
procedure LoadMimeTypes;
|
|
procedure ProcessOptions;
|
|
procedure ProcessOptions;
|
|
procedure ReadConfigFile(const ConfigFile: string);
|
|
procedure ReadConfigFile(const ConfigFile: string);
|
|
|
|
+ procedure SetupCapture(const aFileName: string);
|
|
|
|
+ procedure ShowCaptureOutput(aJSON: TJSONData);
|
|
procedure Usage(Msg: String);
|
|
procedure Usage(Msg: String);
|
|
procedure Writeinfo;
|
|
procedure Writeinfo;
|
|
|
|
+ Public
|
|
|
|
+ Destructor Destroy; override;
|
|
published
|
|
published
|
|
procedure DoLog(EventType: TEventType; const Msg: String); override;
|
|
procedure DoLog(EventType: TEventType; const Msg: String); override;
|
|
Procedure DoRun; override;
|
|
Procedure DoRun; override;
|
|
@@ -133,6 +142,83 @@ begin
|
|
L.Free;
|
|
L.Free;
|
|
end;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
+
|
|
|
|
+function THTTPApplication.GetCaptureJSON(ARequest: TRequest;
|
|
|
|
+ AResponse: TResponse; var aJSON: TJSONData): TJSONArray;
|
|
|
|
+
|
|
|
|
+var
|
|
|
|
+ aJSONObj : TJSONObject absolute aJSON;
|
|
|
|
+ Cont : String;
|
|
|
|
+
|
|
|
|
+begin
|
|
|
|
+ Result:=Nil;
|
|
|
|
+ aJSON:=Nil;
|
|
|
|
+ try
|
|
|
|
+ Cont:=aRequest.Content;
|
|
|
|
+ aJSON:=GetJSON(Cont);
|
|
|
|
+ if aJSON.JSONType<>jtObject then
|
|
|
|
+ Raise EHTTP.Create('No JSON object in capture JSON');
|
|
|
|
+ Result:=aJSONObj.Get('lines',TJSONArray(Nil));
|
|
|
|
+ if Result=Nil then
|
|
|
|
+ begin
|
|
|
|
+ FreeAndNil(aJSON);
|
|
|
|
+ Raise EHTTP.Create('No lines element in capture JSON');
|
|
|
|
+ end;
|
|
|
|
+ except
|
|
|
|
+ On E : Exception do
|
|
|
|
+ begin
|
|
|
|
+ DoLog(etError,Format('Exception %s (%s) : Invalid capture content: not valid JSON: %s',[E.ClassName,E.Message,Copy(Cont,1,255)]));
|
|
|
|
+ aResponse.Code:=400;
|
|
|
|
+ aResponse.CodeText:='INVALID PARAM';
|
|
|
|
+ aResponse.SendResponse;
|
|
|
|
+ end;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure THTTPApplication.ShowCaptureOutput(aJSON : TJSONData);
|
|
|
|
+
|
|
|
|
+var
|
|
|
|
+ S : TJSONStringType;
|
|
|
|
+
|
|
|
|
+begin
|
|
|
|
+ if aJSON.JSONType in StructuredJSONTypes then
|
|
|
|
+ S:=aJSON.AsJSON
|
|
|
|
+ else
|
|
|
|
+ S:=aJSON.AsString;
|
|
|
|
+ if Assigned(FCaptureStream) then
|
|
|
|
+ begin
|
|
|
|
+ S:=S+sLineBreak;
|
|
|
|
+ FCaptureStream.WriteBuffer(S[1],Length(S)*SizeOf(TJSONCharType));
|
|
|
|
+ end
|
|
|
|
+ else
|
|
|
|
+ DoLog(etInfo,'Capture : '+S);
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure THTTPApplication.DoCapture(ARequest: TRequest; AResponse: TResponse);
|
|
|
|
+
|
|
|
|
+Var
|
|
|
|
+ aJSON : TJSONData;
|
|
|
|
+ aArray : TJSONArray;
|
|
|
|
+ I : Integer;
|
|
|
|
+
|
|
|
|
+begin
|
|
|
|
+ aJSON:=Nil;
|
|
|
|
+ aArray:=Nil;
|
|
|
|
+ try
|
|
|
|
+ aArray:=GetCaptureJSON(aRequest,aResponse,aJSON);
|
|
|
|
+ if aArray<>Nil then
|
|
|
|
+ begin
|
|
|
|
+ For I:=0 to aArray.Count-1 do
|
|
|
|
+ ShowCaptureOutput(aArray[i]);
|
|
|
|
+ aResponse.Code:=200;
|
|
|
|
+ aResponse.CodeText:='OK';
|
|
|
|
+ aResponse.SendResponse;
|
|
|
|
+ end;
|
|
|
|
+ finally
|
|
|
|
+ aJSON.Free;
|
|
|
|
+ end;
|
|
|
|
+end;
|
|
|
|
+
|
|
procedure THTTPApplication.Doquit(ARequest: TRequest; AResponse: TResponse);
|
|
procedure THTTPApplication.Doquit(ARequest: TRequest; AResponse: TResponse);
|
|
|
|
|
|
Var
|
|
Var
|
|
@@ -208,6 +294,8 @@ 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');
|
|
|
|
+ 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('-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');
|
|
Writeln('');
|
|
Writeln('');
|
|
@@ -249,8 +337,8 @@ begin
|
|
ProxyManager.RegisterLocation(N,URL).AppendPathInfo:=True;
|
|
ProxyManager.RegisterLocation(N,URL).AppendPathInfo:=True;
|
|
end;
|
|
end;
|
|
|
|
|
|
-
|
|
|
|
-procedure THTTPApplication.ReadConfigFile(const ConfigFile: string);
|
|
|
|
|
|
+Const
|
|
|
|
+ SCaptureRoute = '/debugcapture';
|
|
|
|
|
|
Const
|
|
Const
|
|
SConfig = 'Server';
|
|
SConfig = 'Server';
|
|
@@ -272,6 +360,11 @@ Const
|
|
KeyMaxAge = 'MaxAge';
|
|
KeyMaxAge = 'MaxAge';
|
|
KeyAPI = 'API';
|
|
KeyAPI = 'API';
|
|
KeyCOI = 'CrossOriginIsolation';
|
|
KeyCOI = 'CrossOriginIsolation';
|
|
|
|
+ KeyCapture = 'DebugCapture';
|
|
|
|
+
|
|
|
|
+procedure THTTPApplication.ReadConfigFile(const ConfigFile: string);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
|
|
Var
|
|
Var
|
|
L : TStringList;
|
|
L : TStringList;
|
|
@@ -298,6 +391,8 @@ 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);
|
|
|
|
+ if ValueExists(SConfig,KeyCapture) then
|
|
|
|
+
|
|
L:=TstringList.Create;
|
|
L:=TstringList.Create;
|
|
ReadSectionValues(SProxy,L,[]);
|
|
ReadSectionValues(SProxy,L,[]);
|
|
For I:=0 to L.Count-1 do
|
|
For I:=0 to L.Count-1 do
|
|
@@ -348,6 +443,12 @@ begin
|
|
FMaxAge:=StrToIntDef(GetOptionValue('a','max-age'),FMaxAge);
|
|
FMaxAge:=StrToIntDef(GetOptionValue('a','max-age'),FMaxAge);
|
|
FBackground:=HasOption('b','background');
|
|
FBackground:=HasOption('b','background');
|
|
FCrossOriginIsolation:=hasOption('o','coi');
|
|
FCrossOriginIsolation:=hasOption('o','coi');
|
|
|
|
+ if HasOption('u','capture') then
|
|
|
|
+ begin
|
|
|
|
+ FCaptureFileName:=GetOptionValue('u','capture');
|
|
|
|
+ if FCaptureFileName='' then
|
|
|
|
+ FCaptureFileName:='-';
|
|
|
|
+ end;
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure THTTPApplication.Writeinfo;
|
|
procedure THTTPApplication.Writeinfo;
|
|
@@ -362,8 +463,34 @@ begin
|
|
Log(etInfo,'Proxy location /proxy/%s redirects to %s',[Path,URL]);
|
|
Log(etInfo,'Proxy location /proxy/%s redirects to %s',[Path,URL]);
|
|
if not NoIndexPage then
|
|
if not NoIndexPage then
|
|
Log(etInfo,'Using index page %s',[IndexPageName]);
|
|
Log(etInfo,'Using index page %s',[IndexPageName]);
|
|
|
|
+ if (Self.FPassword<>'') then
|
|
|
|
+ DoLog(etInfo,'/quit route set up');
|
|
|
|
+ if FEcho then
|
|
|
|
+ DoLog(etInfo,'Setting up /echo route');
|
|
Log(etInfo,'Location REST API '+IfThen(FAPISecret<>'','','NOT ')+'activated.');
|
|
Log(etInfo,'Location REST API '+IfThen(FAPISecret<>'','','NOT ')+'activated.');
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+destructor THTTPApplication.Destroy;
|
|
|
|
+begin
|
|
|
|
+ FreeAndNil(FCaptureStream);
|
|
|
|
+ inherited Destroy;
|
|
|
|
+end;
|
|
|
|
+
|
|
|
|
+procedure THTTPApplication.SetupCapture(Const aFileName : string);
|
|
|
|
+
|
|
|
|
+Var
|
|
|
|
+ Dest : String;
|
|
|
|
|
|
|
|
+begin
|
|
|
|
+ if (aFileName<>'') and (aFileName<>'-') then
|
|
|
|
+ begin
|
|
|
|
+ FCaptureStream:=TFileStream.Create(aFileName,fmCreate);
|
|
|
|
+ Dest:='file: '+aFileName
|
|
|
|
+ end
|
|
|
|
+ else
|
|
|
|
+ Dest:='console';
|
|
|
|
+ DoLog(etInfo,Format('Setting up capture on route "%s", writing to %s',[SCaptureRoute,Dest]));
|
|
|
|
+ HTTPRouter.RegisterRoute(SCaptureRoute,rmPost,@DoCapture,False);
|
|
end;
|
|
end;
|
|
|
|
|
|
procedure THTTPApplication.DoRun;
|
|
procedure THTTPApplication.DoRun;
|
|
@@ -373,7 +500,7 @@ Var
|
|
|
|
|
|
begin
|
|
begin
|
|
FMaxAge:=31557600;
|
|
FMaxAge:=31557600;
|
|
- S:=Checkoptions('hqd:ni:p:sH:m:x:c:beQ:a:A:o',['help','quiet','noindexpage','directory:','port:','indexpage:','ssl','hostname:','mimetypes:','proxy:','config:','background','echo','quit:','max-age:','api:','coi']);
|
|
|
|
|
|
+ S:=Checkoptions('hqd:ni:p:sH:m:x:c:beQ:a:A:ou::',['help','quiet','noindexpage','directory:','port:','indexpage:','ssl','hostname:','mimetypes:','proxy:','config:','background','echo','quit:','max-age:','api:','coi','capture']);
|
|
if (S<>'') or HasOption('h','help') then
|
|
if (S<>'') or HasOption('h','help') then
|
|
usage(S);
|
|
usage(S);
|
|
if HasOption('c','config') then
|
|
if HasOption('c','config') then
|
|
@@ -390,8 +517,12 @@ begin
|
|
Log(etError,'Background option not supported');
|
|
Log(etError,'Background option not supported');
|
|
{$endif}
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
+ if FCaptureFileName<>'' then
|
|
|
|
+ SetupCapture(FCaptureFileName);
|
|
if FPassword<>'' then
|
|
if FPassword<>'' then
|
|
|
|
+ begin
|
|
HTTPRouter.RegisterRoute('/quit',rmAll,@Doquit,False);
|
|
HTTPRouter.RegisterRoute('/quit',rmAll,@Doquit,False);
|
|
|
|
+ end;
|
|
if FEcho then
|
|
if FEcho then
|
|
HTTPRouter.RegisterRoute('/echo',rmAll,@DoEcho,False);
|
|
HTTPRouter.RegisterRoute('/echo',rmAll,@DoEcho,False);
|
|
if ProxyManager.LocationCount>0 then
|
|
if ProxyManager.LocationCount>0 then
|