Added GetErrorPageHandler
Some checks failed
TOC Generator / TOC Generator (push) Has been cancelled

This commit is contained in:
Daniele Teti 2024-11-13 23:09:28 +01:00
parent dfd80def94
commit b26a8d6276
7 changed files with 102 additions and 110 deletions

View File

@ -770,8 +770,12 @@ procedure TUnitWebModuleDeclarationCommand.ExecuteImplementation(
var var
activerecord_con_def_name: string; activerecord_con_def_name: string;
activerecord_con_def_filename: string; activerecord_con_def_filename: string;
default_media_type: string;
begin begin
inherited; inherited;
default_media_type := 'TMVCConstants.DEFAULT_CONTENT_TYPE';
Section Section
.AppendLine .AppendLine
.AppendLine('implementation') .AppendLine('implementation')
@ -790,23 +794,28 @@ begin
.AppendLine(' System.IOUtils,') .AppendLine(' System.IOUtils,')
.AppendLine(' MVCFramework.Commons,'); .AppendLine(' MVCFramework.Commons,');
if Model.B[TConfigKey.program_ssv_templatepro] then if Model.B[TConfigKey.program_ssv_templatepro] then
begin begin
Section Section
.AppendLine(' MVCFramework.View.Renderers.TemplatePro,') .AppendLine(' MVCFramework.View.Renderers.TemplatePro,');
default_media_type := 'TMVCMediaType.TEXT_HTML';
end; end;
if Model.B[TConfigKey.program_ssv_webstencils] then if Model.B[TConfigKey.program_ssv_webstencils] then
begin begin
Section Section
.AppendLine(' MVCFramework.View.Renderers.WebStencils,') .AppendLine(' MVCFramework.View.Renderers.WebStencils,');
default_media_type := 'TMVCMediaType.TEXT_HTML';
end; end;
if Model.B[TConfigKey.program_ssv_mustache] then if Model.B[TConfigKey.program_ssv_mustache] then
begin begin
Section Section
.AppendLine(' MVCFramework.View.Renderers.Mustache,') .AppendLine(' MVCFramework.View.Renderers.Mustache,');
default_media_type := 'TMVCMediaType.TEXT_HTML';
end; end;
Section Section
@ -826,7 +835,7 @@ begin
.AppendLine(' // session timeout (0 means session cookie)') .AppendLine(' // session timeout (0 means session cookie)')
.AppendLine(' Config[TMVCConfigKey.SessionTimeout] := dotEnv.Env(''dmvc.session_timeout'', ''0'');') .AppendLine(' Config[TMVCConfigKey.SessionTimeout] := dotEnv.Env(''dmvc.session_timeout'', ''0'');')
.AppendLine(' //default content-type') .AppendLine(' //default content-type')
.AppendLine(' Config[TMVCConfigKey.DefaultContentType] := dotEnv.Env(''dmvc.default.content_type'', TMVCConstants.DEFAULT_CONTENT_TYPE);') .AppendLine(' Config[TMVCConfigKey.DefaultContentType] := dotEnv.Env(''dmvc.default.content_type'', ' + default_media_type + ');')
.AppendLine(' //default content charset') .AppendLine(' //default content charset')
.AppendLine(' Config[TMVCConfigKey.DefaultContentCharset] := dotEnv.Env(''dmvc.default.content_charset'', TMVCConstants.DEFAULT_CONTENT_CHARSET);') .AppendLine(' Config[TMVCConfigKey.DefaultContentCharset] := dotEnv.Env(''dmvc.default.content_charset'', TMVCConstants.DEFAULT_CONTENT_CHARSET);')
.AppendLine(' //unhandled actions are permitted?') .AppendLine(' //unhandled actions are permitted?')

View File

@ -1,5 +1,4 @@
object MyWebModule: TMyWebModule object MyWebModule: TMyWebModule
OldCreateOrder = False
OnCreate = WebModuleCreate OnCreate = WebModuleCreate
OnDestroy = WebModuleDestroy OnDestroy = WebModuleDestroy
Actions = <> Actions = <>

View File

@ -46,6 +46,7 @@ begin
var var
lColor: string; lColor: string;
begin begin
WebContext.Response.StatusCode := HTTP_STATUS.InternalServerError;
if E is EMyException then if E is EMyException then
begin begin
case EMyException(E).Severity of case EMyException(E).Severity of

View File

@ -52,8 +52,7 @@ uses
type type
TMVCHTTPMethodType = (httpGET, httpPOST, httpPUT, httpDELETE, httpPATCH, httpHEAD, httpOPTIONS, TMVCHTTPMethodType = (httpGET, httpPOST, httpPUT, httpDELETE, httpPATCH, httpHEAD, httpOPTIONS, httpTRACE);
httpTRACE);
TMVCHTTPMethods = set of TMVCHTTPMethodType; TMVCHTTPMethods = set of TMVCHTTPMethodType;
@ -167,6 +166,7 @@ type
MaxRequestSize = 'max_request_size'; // bytes MaxRequestSize = 'max_request_size'; // bytes
HATEOSPropertyName = 'hateos'; HATEOSPropertyName = 'hateos';
LoadSystemControllers = 'load_system_controllers'; LoadSystemControllers = 'load_system_controllers';
ErrorPageURL = 'error_page_url';
end; end;
TMVCHostingFrameworkType = (hftUnknown, hftIndy, hftApache, hftISAPI); TMVCHostingFrameworkType = (hftUnknown, hftIndy, hftApache, hftISAPI);
@ -557,15 +557,6 @@ type
function AddPair(const Key, Value: String): TMVCStringPairList; function AddPair(const Key, Value: String): TMVCStringPairList;
end; end;
// TMVCViewDataSet = class(TObjectDictionary<string, TDataset>)
// private
// { private declarations }
// protected
// { protected declarations }
// public
// constructor Create;
// end;
TMVCCriticalSectionHelper = class helper for TCriticalSection TMVCCriticalSectionHelper = class helper for TCriticalSection
public public
procedure DoWithLock(const AAction: TProc); procedure DoWithLock(const AAction: TProc);
@ -844,13 +835,15 @@ procedure dotEnvConfigure(const dotEnvDelegate: TFunc<IMVCDotEnv>);
implementation implementation
uses uses
MVCFramework,
IdCoder3to4, IdCoder3to4,
System.NetEncoding, System.NetEncoding,
System.Character, System.Character,
MVCFramework.Serializer.JsonDataObjects, MVCFramework.Serializer.JsonDataObjects,
MVCFramework.Utils, MVCFramework.Utils,
System.RegularExpressions, System.RegularExpressions,
MVCFramework.Logger, MVCFramework.Serializer.Commons; MVCFramework.Logger,
MVCFramework.Serializer.Commons;
var var
GlobalAppName, GlobalAppPath, GlobalAppExe: string; GlobalAppName, GlobalAppPath, GlobalAppExe: string;
@ -944,11 +937,6 @@ begin
Exit(True); Exit(True);
end; end;
// function IP2Long(const AIP: string): UInt32;
// begin
// Result := IdGlobal.IPv4ToUInt32(AIP);
// end;
function B64Encode(const aValue: string): string; overload; function B64Encode(const aValue: string): string; overload;
begin begin
// Do not use TNetEncoding // Do not use TNetEncoding

View File

@ -43,11 +43,10 @@ uses
MVCFramework.Serializer.Defaults, MVCFramework.Serializer.Defaults,
MVCFramework.Serializer.Intf, MVCFramework.Serializer.Intf,
MVCFramework.DuckTyping, MVCFramework.DuckTyping,
MVCFramework.Cache,
TemplatePro,
Data.DB, Data.DB,
System.Rtti, System.Rtti,
JsonDataObjects; JsonDataObjects,
TemplatePro;
{$WARNINGS OFF} {$WARNINGS OFF}
@ -94,11 +93,21 @@ function DumpAsJSONString(const aValue: TValue; const aParameters: TArray<string
var var
lWrappedList: IMVCList; lWrappedList: IMVCList;
begin begin
if not aValue.IsObject then if aValue.IsEmpty then
begin
Exit('');
end
else if not aValue.IsObject then
begin begin
if aValue.IsType<Int64> then if aValue.IsType<Int64> then
begin begin
Exit(aValue.AsInt64); Exit(aValue.AsInt64);
end else if aValue.IsType<Integer> then
begin
Exit(aValue.AsInteger);
end else if aValue.IsType<string> then
begin
Exit(aValue.AsString);
end; end;
Exit('(Error: Cannot serialize non-object as JSON)'); Exit('(Error: Cannot serialize non-object as JSON)');
end; end;
@ -178,7 +187,7 @@ begin
begin begin
for lPair in ViewModel do for lPair in ViewModel do
begin begin
lCompiledTemplate.SetData(lPair.Key, ViewModel[lPair.Key]); lCompiledTemplate.SetData(lPair.Key, lPair.Value);
end; end;
lCompiledTemplate.SetData('LoggedUserName', WebContext.LoggedUser.UserName); lCompiledTemplate.SetData('LoggedUserName', WebContext.LoggedUser.UserName);
end; end;

View File

@ -45,6 +45,7 @@ type
protected protected
procedure RegisterWSFunctions(WSProcessor: TWebStencilsProcessor); procedure RegisterWSFunctions(WSProcessor: TWebStencilsProcessor);
procedure OnGetValue(Sender: TObject; const AObjectName, APropName: string; var AReplaceText: string; var AHandled: Boolean); procedure OnGetValue(Sender: TObject; const AObjectName, APropName: string; var AReplaceText: string; var AHandled: Boolean);
procedure OnGetFile(Sender: TObject; const AFilename: string; var AText: string; var AHandled: Boolean);
public public
class function GetTValueVarAsString(const Value: TValue; const VarName: string; const Processor: TWebStencilsProcessor): String; class function GetTValueVarAsString(const Value: TValue; const VarName: string; const Processor: TWebStencilsProcessor): String;
procedure Execute(const ViewName: string; const Builder: TStringBuilder); override; procedure Execute(const ViewName: string; const Builder: TStringBuilder); override;
@ -152,6 +153,20 @@ begin
end); end);
end; end;
procedure TMVCWebStencilsViewEngine.OnGetFile(Sender: TObject; const AFilename: string; var AText: string; var AHandled: Boolean);
var
lFName: String;
begin
AHandled := False;
if TPath.IsRelativePath(AFilename) then
begin
lFName := TPath.Combine(FViewPath, AFilename);
lFName := TPath.ChangeExtension(lfname, FDefaultViewFileExtension);
lFName := TPath.Combine(AppPath, lfname);
AText := TFile.ReadAllText(lfname);
AHandled := True;
end;
end;
procedure TMVCWebStencilsViewEngine.OnGetValue(Sender: TObject; const AObjectName, APropName: string; var AReplaceText: string; var AHandled: Boolean); procedure TMVCWebStencilsViewEngine.OnGetValue(Sender: TObject; const AObjectName, APropName: string; var AReplaceText: string; var AHandled: Boolean);
var var
@ -229,6 +244,8 @@ begin
lWebStencilsProcessor.InputFileName := lViewFileName; lWebStencilsProcessor.InputFileName := lViewFileName;
lWebStencilsProcessor.PathTemplate := Config[TMVCConfigKey.ViewPath]; lWebStencilsProcessor.PathTemplate := Config[TMVCConfigKey.ViewPath];
lWebStencilsProcessor.WebRequest := WebContext.Request.RawWebRequest; lWebStencilsProcessor.WebRequest := WebContext.Request.RawWebRequest;
lWebStencilsProcessor.OnFile := OnGetFile;
if Assigned(ViewModel) then if Assigned(ViewModel) then
begin begin
for lPair in ViewModel do for lPair in ViewModel do
@ -239,6 +256,7 @@ begin
end; end;
if Assigned(WebContext.LoggedUser) then if Assigned(WebContext.LoggedUser) then
begin begin
lWebStencilsProcessor.UserLoggedIn := True;
lWebStencilsProcessor.UserRoles := WebContext.LoggedUser.Roles.ToString; lWebStencilsProcessor.UserRoles := WebContext.LoggedUser.Roles.ToString;
end; end;
if Assigned(FBeforeRenderCallback) then if Assigned(FBeforeRenderCallback) then

View File

@ -940,7 +940,7 @@ type
/// PageFragment ignore header and footer views /// PageFragment ignore header and footer views
/// </summary> /// </summary>
function Page(const AViewNames: TArray<string>; const UseCommonHeadersAndFooters: Boolean = True; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline; function Page(const AViewNames: TArray<string>; const UseCommonHeadersAndFooters: Boolean = True; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline;
function Page(const AViewName: string; const UseCommonHeadersAndFooters: Boolean = True; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline; function Page(const AViewName: string; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline;
/// <summary> /// <summary>
/// Page calls GetRenderedView with sensible defaults. /// Page calls GetRenderedView with sensible defaults.
@ -950,25 +950,13 @@ type
function Page(const AViewNames: TArray<string>; const JSONModel: TJSONObject; function Page(const AViewNames: TArray<string>; const JSONModel: TJSONObject;
const UseCommonHeadersAndFooters: Boolean = True; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline; const UseCommonHeadersAndFooters: Boolean = True; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline;
/// <summary>
/// PageFragment calls GetRenderedView.
/// PageFragment ignore header and footer views.
/// </summary>
function PageFragment(const AViewNames: TArray<string>; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline;
/// <summary>
/// PageFragment calls GetRenderedView.
/// PageFragment ignore header and footer views.
/// </summary>
function PageFragment(const AViewNames: TArray<string>; const JSONModel: TJSONObject; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback = nil): string; overload; inline;
/// <summary> /// <summary>
/// Load mustache view located in TMVCConfigKey.ViewsPath /// Load mustache view located in TMVCConfigKey.ViewsPath
/// returns the rendered views and generates output using /// returns the rendered views and generates output using
/// models pushed using Push* methods. /// models pushed using Push* methods.
/// Do not use thie method directly. Use Page and PageFragment, instead. /// Do not use thie method directly. Use Page, instead.
/// </summary> /// </summary>
function LoadView(const AViewNames: TArray<string>; const JSONModel: TJSONObject = nil): string; virtual; //function LoadView(const AViewNames: TArray<string>; const JSONModel: TJSONObject = nil): string; virtual;
function SessionAs<T: TMVCWebSession>: T; function SessionAs<T: TMVCWebSession>: T;
procedure RaiseSessionExpired; virtual; procedure RaiseSessionExpired; virtual;
@ -1297,6 +1285,8 @@ type
FOutput: string; FOutput: string;
FController: TMVCController; FController: TMVCController;
protected protected
FViewPath: string;
FDefaultViewFileExtension: string;
FUseViewCache: Boolean; FUseViewCache: Boolean;
FJSONModel: TJSONObject; FJSONModel: TJSONObject;
FBeforeRenderCallback: TMVCSSVBeforeRenderCallback; FBeforeRenderCallback: TMVCSSVBeforeRenderCallback;
@ -1329,6 +1319,7 @@ type
function IsShuttingDown: Boolean; function IsShuttingDown: Boolean;
procedure EnterInShutdownState; procedure EnterInShutdownState;
function GetErrorPageHandler(const ErrorPageURL: String): TMVCExceptionHandlerProc;
type type
IMVCResponseBuilder = interface IMVCResponseBuilder = interface
@ -1404,6 +1395,30 @@ begin
gIsShuttingDown := True; gIsShuttingDown := True;
end; end;
function GetErrorPageHandler(const ErrorPageURL: String): TMVCExceptionHandlerProc;
begin
Result := procedure(E: Exception; SelectedController: TMVCController; WebContext: TWebContext; var ExceptionHandled: Boolean)
var
lRedirectionURL: String;
begin
if E is EMVCException then
begin
WebContext.Response.Content :=
Format('HTTP %d - %s: %s', [EMVCException(E).HTTPStatusCode, E.ClassName, E.Message]);
end
else
begin
WebContext.Response.Content :=
Format('HTTP %d - %s: %s', [HTTP_STATUS.InternalServerError, E.ClassName, E.Message]);
end;
WebContext.Response.StatusCode := HTTP_STATUS.Found;
lRedirectionURL := ErrorPageURL + '?class=' + URLEncode(E.ClassName) + '&error=' + URLEncode(E.Message);
WebContext.Response.SetCustomHeader('location', lRedirectionURL);
ExceptionHandled := True;
end;
end;
function GetRequestShortDescription(const AWebRequest: TWebRequest): String; function GetRequestShortDescription(const AWebRequest: TWebRequest): String;
begin begin
Result := Format('%s %s%s', [AWebRequest.Method, AWebRequest.PathInfo, Result := Format('%s %s%s', [AWebRequest.Method, AWebRequest.PathInfo,
@ -3010,24 +3025,6 @@ begin
end; // end-execute-routing end; // end-execute-routing
end; // if not handled by beforerouting end; // if not handled by beforerouting
except except
on ESess: EMVCSessionExpiredException do
begin
if not CustomExceptionHandling(ESess, lSelectedController, lContext) then
begin
Log.Error('[%s] %s [PathInfo "%s"] - %d %s (Custom message: "%s")',
[
ESess.Classname,
ESess.Message,
GetRequestShortDescription(ARequest),
ESess.HTTPStatusCode,
HTTP_STATUS.ReasonStringFor(ESess.HTTPStatusCode),
ESess.DetailedMessage
], LOGGERPRO_TAG);
lContext.SessionStop;
lSelectedController.ResponseStatus(ESess.HTTPStatusCode);
lSelectedController.Render(ESess);
end;
end;
on E: EMVCException do on E: EMVCException do
begin begin
if not CustomExceptionHandling(E, lSelectedController, lContext) then if not CustomExceptionHandling(E, lSelectedController, lContext) then
@ -3041,6 +3038,10 @@ begin
HTTP_STATUS.ReasonStringFor(E.HTTPStatusCode), HTTP_STATUS.ReasonStringFor(E.HTTPStatusCode),
E.DetailedMessage E.DetailedMessage
], LOGGERPRO_TAG); ], LOGGERPRO_TAG);
if lContext.SessionStarted then
begin
lContext.SessionStop;
end;
if Assigned(lSelectedController) then if Assigned(lSelectedController) then
begin begin
lSelectedController.ResponseStatus(E.HTTPStatusCode); lSelectedController.ResponseStatus(E.HTTPStatusCode);
@ -3052,30 +3053,6 @@ begin
end; end;
end; end;
end; end;
on EIO: EInvalidOp do
begin
if not CustomExceptionHandling(EIO, lSelectedController, lContext) then
begin
Log.Error('[%s] %s [PathInfo "%s"] - %d %s (Custom message: "%s")',
[
EIO.Classname,
EIO.Message,
GetRequestShortDescription(ARequest),
HTTP_STATUS.InternalServerError,
HTTP_STATUS.ReasonStringFor(HTTP_STATUS.InternalServerError),
'Invalid Op'], LOGGERPRO_TAG);
if Assigned(lSelectedController) then
begin
lSelectedController.ResponseStatus(HTTP_STATUS.InternalServerError);
lSelectedController.Render(EIO);
end
else
begin
SendHTTPStatus(lContext, HTTP_STATUS.InternalServerError,
Format('[%s] %s', [EIO.Classname, EIO.Message]), EIO.Classname);
end;
end;
end;
on Ex: Exception do on Ex: Exception do
begin begin
if Ex is ESqidsException then if Ex is ESqidsException then
@ -4422,19 +4399,19 @@ begin
Result := FViewModel; Result := FViewModel;
end; end;
function TMVCController.LoadView(const AViewNames: TArray<string>; const JSONModel: TJSONObject = nil): string; //function TMVCController.LoadView(const AViewNames: TArray<string>; const JSONModel: TJSONObject = nil): string;
begin //begin
try // try
Result := GetRenderedView(AViewNames, JSONModel); // Result := GetRenderedView(AViewNames, JSONModel);
ResponseStream.Append(Result); // ResponseStream.Append(Result);
except // except
on E: Exception do // on E: Exception do
begin // begin
Log.Error('[%s] %s', [E.Classname, E.Message], LOGGERPRO_TAG); // Log.Error('[%s] %s', [E.Classname, E.Message], LOGGERPRO_TAG);
raise; // raise;
end; // end;
end; // end;
end; //end;
procedure TMVCController.MVCControllerAfterCreate; procedure TMVCController.MVCControllerAfterCreate;
begin begin
@ -4469,20 +4446,9 @@ begin
Result := GetRenderedView(AViewNames, JSONModel, OnBeforeRenderCallback) Result := GetRenderedView(AViewNames, JSONModel, OnBeforeRenderCallback)
end; end;
function TMVCController.Page(const AViewName: string; const UseCommonHeadersAndFooters: Boolean; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string; function TMVCController.Page(const AViewName: string; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string;
begin begin
Result := Page([AViewName], UseCommonHeadersAndFooters, OnBeforeRenderCallback); Result := Page([AViewName], False, OnBeforeRenderCallback);
end;
function TMVCController.PageFragment(const AViewNames: TArray<string>;
const JSONModel: TJSONObject; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string;
begin
Result := Page(AViewNames, JSONModel, False, OnBeforeRenderCallback);
end;
function TMVCController.PageFragment(const AViewNames: TArray<string>; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string;
begin
Result := Page(AViewNames, nil, False);
end; end;
function TMVCController.Page(const AViewNames: TArray<string>; const UseCommonHeadersAndFooters: Boolean; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string; function TMVCController.Page(const AViewNames: TArray<string>; const UseCommonHeadersAndFooters: Boolean; const OnBeforeRenderCallback: TMVCSSVBeforeRenderCallback): string;
@ -5291,6 +5257,8 @@ begin
FContentType := AContentType; FContentType := AContentType;
FOutput := EmptyStr; FOutput := EmptyStr;
FUseViewCache := Engine.fConfigCache_UseViewCache; FUseViewCache := Engine.fConfigCache_UseViewCache;
FViewPath := Engine.Config[TMVCConfigKey.ViewPath];
FDefaultViewFileExtension := WebContext.Config[TMVCConfigKey.DefaultViewFileExtension];
end; end;
constructor TMVCBaseViewEngine.Create( constructor TMVCBaseViewEngine.Create(