Repository navigation
[Bug]: Access Violation in client when server raises exceptions in route handlers #117
Description
RAL Version:
1.0
IDE:
Delphi 10.4 Sydney
Operating System:
Windows
Bug Description
Bug Report: Access Violation in client when server raises exceptions in route handlers
Summary
When a route handler on the server raises an exception, the client receives an Access Violation instead of a valid HTTP error response. There are 3 related bugs causing this behavior.
Version
PascalRAL v0.12.2-1 beta
Problem Description
If a route handler (TRALRequest/TRALResponse) raises an exception and OnServerError is not assigned and RaiseError is false ( as default ),, the server does not send a valid HTTP response to the client. The client attempts to process an incomplete response and causes an Access Violation.
If I change RaiseError to True ( not default value ), the access violation dissapear.
Identified Bugs
Bug 1: Server does not send error response (RALSynopseServer.pas)
File: src/engine/synopse/RALSynopseServer.pas
Lines: 302-308
Current code:
except
on e: exception do
if Assigned(OnServerError) then
OnServerError(e)
else if RaiseError then
raise;
end;Problem: If OnServerError is not assigned and RaiseError = False, the exception is swallowed and no HTTP response is sent to the client.
Impact: The client waits for a response that never arrives.
Bug 2: Client does not check nil before accessing parameters (RALSynopseClient.pas)
File: src/engine/synopse/RALSynopseClient.pas
Line: 148
Current code:
AResponse.Params.AddParam('Stream', AResponse.ParamByName('ral_body').AsStream, rpkBODY);Problem: It does not check if ParamByName('ral_body') is nil before calling AsStream. If the server sends an error response without a body, this causes an Access Violation.
Impact: Access Violation on the client when the server responds without a body.
Bug 3: SetAsStream does not handle nil (RALParams.pas)
File: src/base/RALParams.pas
Lines: 419-428
Current code:
procedure TRALParam.SetAsStream(const AValue: TStream);
begin
if FContent <> nil then
FreeAndNil(FContent);
AValue.Position := 0; // ← Access Violation if AValue is nil
FContent := TRALStringStream.Create(AValue);
FContent.Position := 0;
end;Problem: It does not check if AValue is nil before accessing AValue.Position.
Impact: Access Violation when attempting to assign a nil stream.
Steps to Reproduce
- Create a route handler that raises an exception:scal
procedure TFormServer.FServer_Handle_Route_Test(ARequest: TRALRequest; AResponse: TRALResponse);
begin
raise Exception.Create('Test error');
end;2. Configure the server withRaiseError = Falseand withoutOnServerError:
FServer.RaiseError := False;
// FServer.OnServerError := nil; (by default)3. Call from the client:
FClient.Post('api/test', nil);4. Result: Access Violation on the client atRALSynopseClient.pasline 148.
Expected Behavior
- The server should send an HTTP 500 (Internal Server Error) response with a JSON error when an exception occurs.
- The client should handle error responses without a body without causing an Access Violation.
SetAsStreamshould correctly handle thenilcase.
Proposed Solution
Fix 1: RALSynopseServer.pas
except
on e: exception do
begin
if Assigned(OnServerError) then
OnServerError(e)
else if RaiseError then
raise
else
begin
// Send HTTP 500 error response to client
if vResponse <> nil then
begin
vResponse.StatusCode := HTTP_InternalError;
vResponse.ContentType := rctAPPLICATIONJSON;
vResponse.ResponseText := Format('{"Status":"ERROR","Message":"%s"}',
[StringReplace(e.Message, '"', '"', [rfReplaceAll])]);
AContext.OutContent := vResponse.ResponseText;
AContext.OutContentType := vResponse.ContentType;
AContext.OutCustomHeaders := 'Content-Type: ' + string(vResponse.ContentType) + HTTPLineBreak;
Result := HTTP_InternalError;
end;
end;
end;
end
;### Fix 2: RALSynopseClient.pas
var vBodyParam := AResponse.ParamByName('ral_body');
if vBodyParam <> nil then
AResponse.Params.AddParam('Stream', vBodyParam.AsStream, rpkBODY);
Fix 3: RALParams.pas
procedure TRALParam.SetAsStream(const AValue: TStream);
begin
if FContent <> nil then
FreeAndNil(FContent);
if AValue <> nil then
begin
AValue.Position := 0;
FContent := TRALStringStream.Create(AValue);
FContent.Position := 0;
end
else
FContent := nil;
end;---
Environment
- Delphi: D10
- Engine: mORMot2 (Synopse)
- OS: Windows 10
Priority
High — causes Access Violation and blocks production use.
Steps to reproduce the bug
In bug description
Expected behavior
No response
Pictures of the issue
No response
Referral
No response
Activity
mobius1qwe commentedon Nov 17, 2025
teste 117.zip
fix:
mobius1qwe commentedon Nov 17, 2025
A Fix was just submitted to the
Devbranch, could you please confirm that it solves the issue?Thanks in advance
macc2010 commentedon Nov 18, 2025
The problem is here :
constructor TRALStringStream.Create(AStream: TStream);
begin
inherited Create;
WriteStream(AStream);
end;
AStream comes with nil value, so I have changed by :
constructor TRALStringStream.Create(AStream: TStream);
begin
inherited Create;
if AStream <> nil then
WriteStream(AStream);
end;
And the problem has been solved
Thank you
mobius1qwe commentedon Nov 18, 2025
Could you provide a sample that reproduces the issue? Zip it and attach to the issue here please
macc2010 commentedon Nov 18, 2025
Now I have not a project that I can upload, but the server is a vcl project ( not a console ), and the client is a vcl project, in server I am using TRALSynopseServer with TRALServerBasicAuth and in client a TRALClient with EngineType := 'mORMot2' and using FClient.Post ( synchronous ) and after that I use Response1.ResponseText to see the server response.
macc2010 commentedon Nov 18, 2025
I have seen that this code :
procedure TRALParam.SetAsStream(const AValue: TStream);
begin
if FContent <> nil then
FreeAndNil(FContent);
if AValue <> nil then
begin
AValue.Position := 0;
FContent := TRALStringStream.Create(AValue);
FContent.Position := 0;
end
else
FContent := nil; <----------------- memory leak when finish app if FContent had a value
end;
Should be this :
procedure TRALParam.SetAsStream(const AValue: TStream);
begin
if FContent <> nil then
FreeAndNil(FContent);
if AValue <> nil then
begin
AValue.Position := 0;
FContent := TRALStringStream.Create(AValue);
FContent.Position := 0;
end
else
FreeAndNil(FContent);
end;
Is not it?
mobius1qwe commentedon Nov 18, 2025
That line is redundant, its just a fix to check if your AV stopped happening, the first thing the method does is verify if FContent is assigned or not, if it is, it gets destroyed and set to nil before checking if the AValue should be assigned to it.
Keep in mind that all this checks and verifications are happening while a request is being sent to the server, so it has to be fast and precise, no room for redundancies as it will slow down the server a lot.
macc2010 commentedon Nov 18, 2025
I was in a mistaken, FContent is needed in more sites
I think that the solution should to be this :
procedure TRALParam.SetAsStream(const AValue: TStream);
begin
if AValue <> nil then
AValue.Position := 0;
FreeAndNil(FContent);
FContent := TRALStringStream.Create(AValue);
FContent.Position := 0;
end;
and this :
constructor TRALStringStream.Create(AStream: TStream);
begin
inherited Create;
if AStream <> nil then
WriteStream(AStream);
end;
mobius1qwe commentedon Nov 18, 2025
I think it's easier if you make a simple sample that represents your issue and attach it here on the issue, that way I can see how the error happens and make a quick fix for it. You could grab the attached zip that I posted to test the issue and modify it to represent your environment, then you re-attach it here so it would be faster to find the issue and fix it