Browse Source

+ Allow customization of response through callback
+ Allow customization of response through custom headers
+ Log error conditions (403,400,404)

Michaël Van Canneyt 9 months ago
parent
commit
18e519963f
1 changed files with 69 additions and 12 deletions
  1. 69 12
      packages/fcl-web/src/base/fpwebfile.pp

+ 69 - 12
packages/fcl-web/src/base/fpwebfile.pp

@@ -36,6 +36,8 @@ Type
   { TFPCustomFileModule }
   { TFPCustomFileModule }
 
 
   TFPCustomFileModule = Class(TCustomHTTPModule)
   TFPCustomFileModule = Class(TCustomHTTPModule)
+  private
+    class var _globalHeaders : TstringList;
   private
   private
     FCacheControlMaxAge: Integer;
     FCacheControlMaxAge: Integer;
     FScriptName:string;
     FScriptName:string;
@@ -48,14 +50,23 @@ Type
     Function AllowFile(Const AFileName : String) : Boolean; virtual;
     Function AllowFile(Const AFileName : String) : Boolean; virtual;
     // Actually Send file to client.
     // Actually Send file to client.
     Procedure SendFile(Const AFileName : String; AResponse : TResponse); virtual;
     Procedure SendFile(Const AFileName : String; AResponse : TResponse); virtual;
+    // Prepare response. This can be overridden to add headers
+    procedure PrepareResponse(aResponse: TResponse); virtual;
   Public
   Public
     Constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override; overload;
     Constructor CreateNew(AOwner: TComponent; CreateMode: Integer); override; overload;
     // Overrides TCustomHTTPModule to implement file serving.
     // Overrides TCustomHTTPModule to implement file serving.
     Procedure HandleRequest(ARequest : TRequest; AResponse : TResponse); override;
     Procedure HandleRequest(ARequest : TRequest; AResponse : TResponse); override;
     Property CacheControlMaxAge : Integer Read FCacheControlMaxAge Write FCacheControlMaxAge;
     Property CacheControlMaxAge : Integer Read FCacheControlMaxAge Write FCacheControlMaxAge;
-  Public Class Var
-    // If you want some logging, set this.
-    OnLog : TSimpleFileLog;
+  Public
+    Type
+      TPrepareFileResponseCallback = procedure(Sender : TObject; aResponse : TResponse) of object;
+    Class Var
+      // If you want some logging, set this.
+      OnLog : TSimpleFileLog;
+      // If you want to customize the response, call this.
+      OnPrepareResponse : TPrepareFileResponseCallback;
+      // You can add some global headers.
+    class procedure RegisterGlobalResponseHeader(const aName,aValue : String);
   Published
   Published
     Property CORS;
     Property CORS;
     property Kind;
     property Kind;
@@ -173,6 +184,7 @@ Procedure RegisterFileLocation(Const ALocation,ADirectory : String);
 
 
 Var
 Var
   D,BaseDir : String;
   D,BaseDir : String;
+  C : TFPCustomFileModuleClass;
 
 
 begin
 begin
   if (ALocation='') then
   if (ALocation='') then
@@ -181,8 +193,9 @@ begin
     Raise HTTPError.CreateFmt(SErrInvalidLocation,[aLocation]);
     Raise HTTPError.CreateFmt(SErrInvalidLocation,[aLocation]);
   if (Locations=Nil) then
   if (Locations=Nil) then
     Locations:=TStringList.Create;
     Locations:=TStringList.Create;
-  if DefaultFileModuleClass=Nil then
-    DefaultFileModuleClass:=TFPCustomFileModule;
+  C:=DefaultFileModuleClass;
+  if C=Nil then
+    C:=TFPCustomFileModule;
   BaseDir:=ExtractFilePath(ParamStr(0));
   BaseDir:=ExtractFilePath(ParamStr(0));
   if (ADirectory='') then
   if (ADirectory='') then
     setFileLocationPath(aLocation,BaseDir)
     setFileLocationPath(aLocation,BaseDir)
@@ -195,7 +208,8 @@ begin
       Raise HTTPError.CreateFmt(SErrInvalidDirectory,[D]);
       Raise HTTPError.CreateFmt(SErrInvalidDirectory,[D]);
     SetFileLocationPath(ALocation,IncludeTrailingPathDelimiter(D));
     SetFileLocationPath(ALocation,IncludeTrailingPathDelimiter(D));
     end;
     end;
-  RegisterHTTPModule(ALocation,DefaultFileModuleClass,true);
+  Writeln('Location ',aLocation,' handled by ',C.ClassName);
+  RegisterHTTPModule(ALocation,C,true);
   ModuleFactory.MoveModuleBeforeDefault(ALocation);
   ModuleFactory.MoveModuleBeforeDefault(ALocation);
 end;
 end;
 
 
@@ -235,6 +249,7 @@ begin
   aClass:=DefaultSimpleFileModuleClass;
   aClass:=DefaultSimpleFileModuleClass;
   if aClass=Nil then
   if aClass=Nil then
     aClass:=TSimpleFileModule;
     aClass:=TSimpleFileModule;
+  Writeln('Creating ',aClass.ClassName);
   With aClass.CreateNew(Nil) do
   With aClass.CreateNew(Nil) do
     try
     try
       HandleRequest(ARequest,AResponse);
       HandleRequest(ARequest,AResponse);
@@ -287,7 +302,7 @@ begin
   Result:=FPrivateRoute<>Nil;
   Result:=FPrivateRoute<>Nil;
 end;
 end;
 
 
-Function TFPCustomFileModule.GetRequestFileName(Const ARequest : TRequest) : String;
+function TFPCustomFileModule.GetRequestFileName(const ARequest: TRequest): String;
 
 
   procedure sb;
   procedure sb;
 
 
@@ -305,7 +320,7 @@ begin
   sb;
   sb;
 end;
 end;
 
 
-Function TFPCustomFileModule.MapFileName(Const AFileName : String) : String; 
+function TFPCustomFileModule.MapFileName(const AFileName: String): String;
 
 
 Var
 Var
   D,localBaseURL : String;
   D,localBaseURL : String;
@@ -333,7 +348,7 @@ begin
     end;
     end;
 end;
 end;
 
 
-Function TFPCustomFileModule.AllowFile(Const AFileName : String) : Boolean;
+function TFPCustomFileModule.AllowFile(const AFileName: String): Boolean;
 
 
 Var
 Var
   BaseDir,FN : String;
   BaseDir,FN : String;
@@ -352,7 +367,7 @@ begin
   Result:=Pos('..'+PathDelim,FN)=0;
   Result:=Pos('..'+PathDelim,FN)=0;
 end;
 end;
 
 
-procedure TFPCustomFileModule.SendFile(Const AFileName : String; AResponse : TResponse);
+procedure TFPCustomFileModule.SendFile(const AFileName: String; AResponse: TResponse);
 
 
 Var
 Var
   F : TFileStream;
   F : TFileStream;
@@ -376,19 +391,40 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure TFPCustomFileModule.PrepareResponse(aResponse: TResponse);
+
+var
+  I : Integer;
+  N,V : String;
+
+begin
+  if Assigned(_globalHeaders) then
+    For I:=0 to _GlobalHeaders.Count-1 do
+      begin
+      _GlobalHeaders.GetNameValue(I,N,V);
+      aResponse.CustomHeaders.Values[N]:=V;
+      end;
+  if Assigned(OnPrepareResponse) then
+     OnPrepareResponse(Self,aResponse);
+end;
+
 constructor TFPCustomFileModule.CreateNew(AOwner: TComponent; CreateMode: Integer);
 constructor TFPCustomFileModule.CreateNew(AOwner: TComponent; CreateMode: Integer);
 begin
 begin
   inherited CreateNew(aOwner,CreateMode);
   inherited CreateNew(aOwner,CreateMode);
   CacheControlMaxAge:=DefaultCacheControlMaxAge;
   CacheControlMaxAge:=DefaultCacheControlMaxAge;
+  Kind:=wkOneShot;
 end;
 end;
 
 
 
 
-Procedure TFPCustomFileModule.HandleRequest(ARequest : TRequest; AResponse : TResponse);
+procedure TFPCustomFileModule.HandleRequest(ARequest: TRequest; AResponse: TResponse);
 
 
 Var
 Var
   RFN,FN : String;
   RFN,FN : String;
+  S : String;
 
 
 begin
 begin
+  WriteStr(S,'Class ',ClassName,' mapping request ',aRequest.ScriptName);
+  Writeln(S);
   FScriptName:=ARequest.ScriptName;
   FScriptName:=ARequest.ScriptName;
   If CompareText(ARequest.Method,'GET')<>0 then
   If CompareText(ARequest.Method,'GET')<>0 then
     begin
     begin
@@ -399,24 +435,44 @@ begin
   if (RFN='') then
   if (RFN='') then
     begin
     begin
     AResponse.SetStatus(400,True);
     AResponse.SetStatus(400,True);
+    if Assigned (OnLog) then
+      OnLog(etInfo,Format('%d serving "%s"',[AResponse.Code,aRequest.URL]));
     exit;
     exit;
     end;
     end;
   FN:=MapFileName(RFN);
   FN:=MapFileName(RFN);
   if (FN='') or not AllowFile(FN) then  
   if (FN='') or not AllowFile(FN) then  
     begin
     begin
     AResponse.SetStatus(403,True);
     AResponse.SetStatus(403,True);
+    if Assigned (OnLog) then
+      OnLog(etInfo,Format('%d serving "%s"',[AResponse.Code,aRequest.URL]));
     exit;
     exit;
     end;
     end;
   if  not FileExists(FN) then
   if  not FileExists(FN) then
     begin
     begin
     AResponse.SetStatus(404,True);
     AResponse.SetStatus(404,True);
+    if Assigned (OnLog) then
+      OnLog(etInfo,Format('%d serving "%s"',[AResponse.Code,aRequest.URL]));
     exit;
     exit;
     end;
     end;
-  SendFile(FN,AResponse);
+  PrepareResponse(aResponse);
+  if not aResponse.ContentSent then
+    SendFile(FN,AResponse)
+  else if Assigned (OnLog) then
+    OnLog(etInfo,Format('Skipping serving "%s", handled in user code',[aRequest.URL]));
   if Assigned (OnLog) then
   if Assigned (OnLog) then
     OnLog(etInfo,Format('%d serving "%s" -> "%s"',[AResponse.Code,RFN,FN]));
     OnLog(etInfo,Format('%d serving "%s" -> "%s"',[AResponse.Code,RFN,FN]));
 end;
 end;
 
 
+class procedure TFPCustomFileModule.RegisterGlobalResponseHeader(const aName, aValue: String);
+begin
+  if not assigned(_GlobalHeaders) then
+    begin
+    _globalHeaders:=TStringList.Create;
+    _globalHeaders.NameValueSeparator:=':';
+    end;
+  _GlobalHeaders.Values[aName]:=aValue;
+end;
+
 procedure TFPWebFileLocationAPIModule.SetCors(AValue: TCORSSupport);
 procedure TFPWebFileLocationAPIModule.SetCors(AValue: TCORSSupport);
 begin
 begin
   if FCors=AValue then Exit;
   if FCors=AValue then Exit;
@@ -689,4 +745,5 @@ initialization
 
 
 finalization
 finalization
   FreeAndNil(Locations);
   FreeAndNil(Locations);
+  FreeAndNil(TFPCustomFileModule._globalHeaders);
 end.
 end.