Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 12 additions & 19 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
jobs:
test:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- uses: actions/checkout@v4
Expand All @@ -18,6 +19,11 @@ jobs:
sudo apt-get update
sudo apt-get install -y fpc

- name: Run FPC exception propagation regression (Tree and Radix)
run: |
sh tests/fpc-exception-propagation/run-exception-propagation-test.sh
sh tests/fpc-exception-propagation/run-exception-propagation-test.sh -dHORSE_PROVIDER_EPOLL

- name: Run FPC WebSocket EAGAIN regression
run: tests/fpc-websocket/run-ws-eagain-test.sh

Expand All @@ -27,26 +33,13 @@ jobs:
- name: Run FPC URL decoding regression
run: sh tests/fpc-url-decoding/run-url-decoding-test.sh

- name: Install Boss Dependency Manager
run: |
curl -L https://github.com/hashload/boss/releases/latest/download/boss-linux -o boss
chmod +x boss
sudo mv boss /usr/local/bin/boss

- name: Install Boss Dependencies
run: |
cd tests/src
boss install

- name: Compile Console Tests
run: |
cd tests/src
fpc -Mdelphi -Sh -O2 -FE.. -Fu"../../src:modules/github_com_hashload_jhonson/src:modules/github_com_viniciussanchez_restrequest4delphi/src:controllers:tests" "-dRR4D_INDY" "-dHORSE_CONSOLE" Console.dpr
# Console.dpr uses Delphi/DUnitX; validate it with run_delphi_tests.ps1.
# The Linux job runs the standalone FPC suites instead.
- name: Run FPC stream factory regression
run: sh tests/stream-factory/run-stream-factory.sh

- name: Run Console Tests
run: |
cd tests
./Console -exit:Continue
- name: Run FPC provider configuration regressions
run: bash tests/provider-config/run-provider-config.sh

fpc-trunk-keepalive:
runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion boss.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "horse",
"description": "Express-inspired web framework for Delphi and Lazarus",
"version": "3.3.13",
"version": "3.3.14",
"homepage": "https://github.com/HashLoad/horse",
"license": "MIT",
"mainsrc": "src/",
Expand Down
2 changes: 1 addition & 1 deletion src/Horse.Constants.pas
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ interface
DEFAULT_HOST = '0.0.0.0';
DEFAULT_PORT = 9000;
START_RUNNING = 'Server is running on %s:%d';
HORSE_VERSION = '3.3.13';
HORSE_VERSION = '3.3.14';

implementation

Expand Down
60 changes: 25 additions & 35 deletions src/Horse.Core.Router.Radix.pas
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,17 @@ function TRadixExecutor.Run: Boolean;
except
on E: Exception do
begin
Result := False;
raise;
// The middleware chain has unwound; only unhandled errors reach this fallback.
if E is EHorseCallbackInterrupted then
raise
else if E is EHorseException then
FResponse.ContentType('application/json; charset=UTF-8')
.Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status)
else if THorse.HasOnError then
THorse.ExecuteOnError(FRequest, FResponse, E)
else if FResponse.Status < Integer(THTTPStatus.BadRequest) then
FResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Result := True;
end;
end;
finally
Expand Down Expand Up @@ -380,21 +389,7 @@ procedure TRadixFlow.Next;
on E: Exception do
begin
FActive := False;
if E is EHorseCallbackInterrupted then
raise;
if E is EHorseException then
begin
FResponse.ContentType('application/json; charset=UTF-8').Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status);
Exit;
end;
if THorse.HasOnError then
begin
THorse.ExecuteOnError(FRequest, FResponse, E);
Exit;
end;
if FResponse.Status < Integer(THTTPStatus.BadRequest) then
FResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Exit;
raise;
end;
end;
end;
Expand Down Expand Up @@ -979,31 +974,26 @@ function THorseRadixRouter.Execute(const ARequest: THorseRequest; const ARespons
end);
end);
Result := LResult;
AResponse.FlushCookiesToWebResponse;
except
on E: Exception do
begin
if THorse.HasOnError then
// The middleware chain has unwound; only unhandled errors reach this fallback.
if E is EHorseCallbackInterrupted then
begin
if E is EHorseCallbackInterrupted then
begin
Result := True;
end
else
begin
THorse.ExecuteOnError(ARequest, AResponse, E);
Result := True;
end;
if not THorse.HasOnError then
raise;
end
else
begin
{$IF DEFINED(FPC)}
Writeln('CRITICAL RADIX ERROR: ', E.ClassName, ': ', E.Message); Flush(Output);
{$ENDIF}
raise;
end;
else if E is EHorseException then
AResponse.ContentType('application/json; charset=UTF-8')
.Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status)
else if THorse.HasOnError then
THorse.ExecuteOnError(ARequest, AResponse, E)
else if AResponse.Status < Integer(THTTPStatus.BadRequest) then
AResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Result := True;
end;
end;
AResponse.FlushCookiesToWebResponse;
finally
LStopwatch.Stop;
THorseCore.ExecuteOnTelemetry(ARequest, AResponse, LStopwatch.Elapsed.TotalMilliseconds);
Expand Down
95 changes: 16 additions & 79 deletions src/Horse.Core.RouterTree.NextCaller.pas
Original file line number Diff line number Diff line change
Expand Up @@ -96,31 +96,10 @@ procedure NextCallerDoPreValidation;
LNextCaller: TNextCaller;
begin
LNextCaller := TNextCaller(GCurrentNextCaller);
try
LNextCaller.FFound^ := True;
if LNextCaller.FCallBack.TryGetValue(LNextCaller.FHTTPType, LCallback) then
begin
THorseCallbackProc(LCallback.Items[LNextCaller.FIndexCallback])(LNextCaller.FRequest, LNextCaller.FResponse, LNextCaller.Next);
end;
except
on E: Exception do
begin
if E is EHorseCallbackInterrupted then
raise;
if E is EHorseException then
begin
LNextCaller.FResponse.ContentType('application/json; charset=UTF-8').Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status);
Exit;
end;
if THorse.HasOnError then
begin
THorse.ExecuteOnError(LNextCaller.FRequest, LNextCaller.FResponse, E);
Exit;
end;
if LNextCaller.FResponse.Status < Integer(THTTPStatus.BadRequest) then
LNextCaller.FResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Exit;
end;
LNextCaller.FFound^ := True;
if LNextCaller.FCallBack.TryGetValue(LNextCaller.FHTTPType, LCallback) then
begin
THorseCallbackProc(LCallback.Items[LNextCaller.FIndexCallback])(LNextCaller.FRequest, LNextCaller.FResponse, LNextCaller.Next);
end;
LNextCaller.Next;
end;
Expand Down Expand Up @@ -242,66 +221,24 @@ procedure TNextCaller.Next;
THorse.ExecutePreValidation(FRequest, FResponse,
procedure
begin
try
FFound^ := True;
{$IF DEFINED(FPC)}
THorseCallbackProc(LCallback.Items[FIndexCallback])(FRequest, FResponse, Next);
{$ELSE}
LCallback[FIndexCallback](FRequest, FResponse, Next);
{$ENDIF}
except
on E: Exception do
begin
if E is EHorseCallbackInterrupted then
raise;
if E is EHorseException then
begin
FResponse.ContentType('application/json; charset=UTF-8').Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status);
Exit;
end;
if THorse.HasOnError then
begin
THorse.ExecuteOnError(FRequest, FResponse, E);
Exit;
end;
if FResponse.Status < Integer(THTTPStatus.BadRequest) then
FResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Exit;
end;
end;
FFound^ := True;
{$IF DEFINED(FPC)}
THorseCallbackProc(LCallback.Items[FIndexCallback])(FRequest, FResponse, Next);
{$ELSE}
LCallback[FIndexCallback](FRequest, FResponse, Next);
{$ENDIF}
Next;
end);
{$ENDIF}
end
else
begin
try
FFound^ := True;
{$IF DEFINED(FPC)}
THorseCallbackProc(LCallback.Items[FIndexCallback])(FRequest, FResponse, Next);
{$ELSE}
LCallback[FIndexCallback](FRequest, FResponse, Next);
{$ENDIF}
except
on E: Exception do
begin
if E is EHorseCallbackInterrupted then
raise;
if E is EHorseException then
begin
FResponse.ContentType('application/json; charset=UTF-8').Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status);
Exit;
end;
if THorse.HasOnError then
begin
THorse.ExecuteOnError(FRequest, FResponse, E);
Exit;
end;
if FResponse.Status < Integer(THTTPStatus.BadRequest) then
FResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Exit;
end;
end;
FFound^ := True;
{$IF DEFINED(FPC)}
THorseCallbackProc(LCallback.Items[FIndexCallback])(FRequest, FResponse, Next);
{$ELSE}
LCallback[FIndexCallback](FRequest, FResponse, Next);
{$ENDIF}
Next;
end;
end;
Expand Down
40 changes: 24 additions & 16 deletions src/Horse.Core.RouterTree.pas
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ implementation
System.SyncObjs,
System.Diagnostics,
{$ENDIF}
Horse.Exception,
Horse.Core.RouterTree.NextCaller,
Horse, Horse.Core;

Expand Down Expand Up @@ -177,8 +178,17 @@ function TRouterTreeExecutor.Run: Boolean;
except
on E: Exception do
begin
Result := False;
raise;
// The middleware chain has unwound; only unhandled errors reach this fallback.
if E is EHorseCallbackInterrupted then
raise
else if E is EHorseException then
FResponse.ContentType('application/json; charset=UTF-8')
.Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status)
else if THorse.HasOnError then
THorse.ExecuteOnError(FRequest, FResponse, E)
else if FResponse.Status < Integer(THTTPStatus.BadRequest) then
FResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Result := True;
end;
end;
finally
Expand Down Expand Up @@ -440,22 +450,20 @@ function THorseRouterTree.Execute(const ARequest: THorseRequest; const AResponse
except
on E: Exception do
begin
if THorse.HasOnError then
// The middleware chain has unwound; only unhandled errors reach this fallback.
if E is EHorseCallbackInterrupted then
begin
if E is EHorseCallbackInterrupted then
begin
Result := True;
end
else
begin
THorse.ExecuteOnError(ARequest, AResponse, E);
Result := True;
end;
if not THorse.HasOnError then
raise;
end
else
begin
raise;
end;
else if E is EHorseException then
AResponse.ContentType('application/json; charset=UTF-8')
.Send(EHorseException(E).ToJSON).Status(EHorseException(E).Status)
else if THorse.HasOnError then
THorse.ExecuteOnError(ARequest, AResponse, E)
else if AResponse.Status < Integer(THTTPStatus.BadRequest) then
AResponse.Send('Internal Application Error: ' + E.Message).Status(THTTPStatus.InternalServerError);
Result := True;
end;
end;
AResponse.FlushCookiesToWebResponse;
Expand Down
Loading
Loading