Hello again) Now implemented the middleware router - AdvancedHTTPRouter. Also few fixes, like ip:port params on the constructor, and accept in ipv6 mode:)
AdvancedHTTPRouter is a high-performance, lightweight HTTP router designed specifically to integrate with your AdvancedHTTPServer. It provides a modern, structured way to define routes, handle requests, and organize application logic, inspired by popular frameworks like Gin (Go) or Echo.
Key Features
Efficient Routing with Radix Tree
Uses a radix tree (trie) for route matching. This ensures very fast lookups, even with hundreds or thousands of routes. It handles:
• Static paths: /users/list• Named parameters: /users/:id → access via ctx.Param('id')• Wildcard catch-all: /files/*path → captures everything after
HTTP Method Support
Dedicated methods for all standard verbs:
GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS• Any() for routes that respond to all methods
• Automatic fallback for HEAD to GET handlers if no explicit HEAD route exists
Middleware System
• Global middleware via router.Use(...)• Per-group middleware
• Middleware can call ctx.Next() to continue the chain or ctx.Abort() to stop it
• Middleware and handlers form a chain — you can attach multiple handlers per route
Route Grouping
Powerful grouping with prefixes and nested groups:
Example structure:
v1 := router.Group("/api/v1")
{
v1.GET("/users", getUsers)
v1.POST("/users", createUser)
}
Sample code:
var
AppState : TAppState;
Server: THTTPServer;
Router: THTTPRouter;
APIGroup: THTTPRouterGroup;
begin
AppState := TAppState.Create;
try
Server := THTTPServer.Create;
try
Server.MaxHeaderBytes := 65536;
Server.MaxBodyBytes := 10 * 1024 * 1024; // 10MB
Router := THTTPRouter.Create(Server);
try
// global middleware (logging)
Router.Use(
procedure(C: TObject)
begin
WriteLn('[LOG] ', THTTPRouterContext(C).R.Method, ' ', THTTPRouterContext(C).R.URL);
THTTPRouterContext(C).Next;
end
);
// Main page - get SPA
Router.GET('/', [@StaticHandler]);
// API group
APIGroup := Router.Group('/api') as THTTPRouterGroup;
APIGroup.Use(@AuthMiddleware); // защищаем весь API
// POST /api/users
APIGroup.POST('/users',
[
procedure(C: TObject)
var
Body: string;
JSON: TJSONObject;
Name, Email: string;
ID: integer;
begin
Body := THTTPRouterContext(C).R.Body;
if Body = '' then
begin
THTTPRouterContext(C).Text(400, 'Empty body');
Exit;
end;
JSON := GetJSON(Body) as TJSONObject;
try
Name := JSON.Get('name', '');
Email := JSON.Get('email', '');
if (Name = '') or (Email = '') then
begin
THTTPRouterContext(C).Text(400, 'Name and email required');
Exit;
end;
ID := AppState.AddUser(Name, Email);
JSON.Integers['id'] := ID;
THTTPRouterContext(C).JSON(201, JSON);
finally
JSON.Free;
end;
end
]
);
// GET /api/users/:id
APIGroup.GET('/users/:id',
[
procedure(C: TObject)
var
ID: integer;
User: TJSONObject;
begin
ID := StrToIntDef(THTTPRouterContext(C).Param('id'), -1);
if ID <= 0 then
begin
THTTPRouterContext(C).Text(400, 'Invalid ID');
Exit;
end;
User := AppState.GetUser(ID);
if not Assigned(User) then
THTTPRouterContext(C).Text(404, 'User not found')
else
begin
THTTPRouterContext(C).JSON(200, User);
User.Free;
end;
end
]
);
// PUT /api/users/:id
APIGroup.PUT('/users/:id',
[
procedure(C: TObject)
var
ID: integer;
Body: string;
JSON: TJSONObject;
Name, Email: string;
begin
ID := StrToIntDef(THTTPRouterContext(C).Param('id'), -1);
if ID <= 0 then
begin
THTTPRouterContext(C).Text(400, 'Invalid ID');
Exit;
end;
if not Assigned(AppState.GetUser(ID)) then
begin
THTTPRouterContext(C).Text(404, 'User not found');
Exit;
end;
Body := THTTPRouterContext(C).R.Body;
JSON := GetJSON(Body) as TJSONObject;
try
Name := JSON.Get('name', '');
Email := JSON.Get('email', '');
AppState.UpdateUser(ID, Name, Email);
THTTPRouterContext(C).Text(204, '');
finally
JSON.Free;
end;
end
]
);
// DELETE /api/users/:id
APIGroup.DELETE('/users/:id',
[
procedure(C: TObject)
var
ID: integer;
begin
ID := StrToIntDef(THTTPRouterContext(C).Param('id'), -1);
if ID <= 0 then
begin
THTTPRouterContext(C).Text(400, 'Invalid ID');
Exit;
end;
AppState.DeleteUser(ID);
THTTPRouterContext(C).Text(204, '');
end
]
);
// GET /api/users
APIGroup.GET('/users',
[
procedure(C: TObject)
begin
THTTPRouterContext(C).JSON(200, AppState.ListUsers);
end
]
);
// Static
Router.Any('/public/*filepath',
[
procedure(C: TObject)
var
Path: string;
FullPath: string;
begin
Path := THTTPRouterContext(C).Param('filepath');
FullPath := 'public/' + StringReplace(Path, '..', '', [rfReplaceAll]);
if FileExists(FullPath) then
ServeFile(THTTPRouterContext(C).W, THTTPRouterContext(C).R, FullPath)
else
THTTPRouterContext(C).Text(404, 'Not Found');
end
]
);
Router.Mount;
WriteLn('Starting server on http://localhost:8080');
Server.ListenAndServe(':8080');
finally
Router.Free;
end;
finally
Server.Free;
end;
finally
AppState.Free;
end;