Basic Web Server
uses
BI.Web.Server.Indy, BI.Web.Context;
type
TWebServerForm = class(TForm)
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
Server: THttpServer;
procedure HandleRequest(const Context: TWebContext);
end;
procedure TWebServerForm.FormCreate(Sender: TObject);
begin
// Create HTTP server (uses Indy)
Server := THttpServer.Create(Self);
Server.Port := 15015;
Server.OnCommandGet := HandleRequest;
Server.Active := True;
ShowMessage('Server started on port 15015');
end;
procedure TWebServerForm.HandleRequest(const Context: TWebContext);
begin
// Handle HTTP GET request
Context.Response.ContentText := 'Hello from TeeBI Web Server';
Context.Response.ContentType := 'text/plain';
end;
procedure TWebServerForm.FormDestroy(Sender: TObject);
begin
Server.Active := False;
Server.Free;
end;
Serve Data as JSON
uses
BI.DataItem, BI.Persist, BI.JSON, BI.Web.Context;
procedure HandleDataRequest(const Context: TWebContext);
var
Data: TDataItem;
JSON: String;
begin
// Load data
Data := TStore.Load('BISamples', 'Products');
try
// Convert to JSON
JSON := TBIJSONExport.AsString(Data);
// Return JSON response
Context.Response.ContentText := JSON;
Context.Response.ContentType := 'application/json';
finally
Data.Free;
end;
end;
Query Parameters
uses
BI.SQL, BI.Web.Context;
procedure HandleQueryRequest(const Context: TWebContext);
var
Data, Result: TDataItem;
Category, JSON: String;
begin
// Get query parameter: /data?category=Electronics
Category := Context.Request.Params.Values['category'];
Data := TStore.Load('BISamples', 'Products');
try
// Apply filter if category provided
if Category <> '' then
Result := TBISQL.From(Data,
'ProductName, UnitPrice where Category = "' + Category + '"')
else
Result := Data;
try
JSON := TBIJSONExport.AsString(Result);
Context.Response.ContentText := JSON;
Context.Response.ContentType := 'application/json';
finally
if Result <> Data then
Result.Free;
end;
finally
Data.Free;
end;
end;
URL Routing
uses
System.SysUtils, BI.Web.Context;
procedure HandleRequest(const Context: TWebContext);
var
Path: String;
begin
Path := Context.Request.Document;
if Path = '/products' then
ServeProducts(Context)
else if Path = '/customers' then
ServeCustomers(Context)
else if StartsText('/query/', Path) then
HandleQuery(Context)
else
begin
Context.Response.ContentText := 'Not Found';
Context.Response.ResponseNo := 404;
end;
end;
Binary Data Format
uses
BI.Persist, System.Classes;
// Serve data in TeeBI binary format (faster, compressed)
procedure ServeBinaryData(const Context: TWebContext);
var
Data: TDataItem;
Stream: TMemoryStream;
begin
Data := TStore.Load('BISamples', 'Products');
try
Stream := TMemoryStream.Create;
try
// Save to binary stream
TDataItemPersistence.SaveToStream(Data, Stream);
Stream.Position := 0;
Context.Response.ContentStream := Stream;
Context.Response.ContentType := 'application/octet-stream';
except
Stream.Free;
raise;
end;
finally
Data.Free;
end;
end;
CORS Support
uses BI.Web.Context;
// Enable Cross-Origin Resource Sharing
procedure EnableCORS(const Context: TWebContext);
begin
with Context.Response.CustomHeaders do
begin
Add('Access-Control-Allow-Origin=*');
Add('Access-Control-Allow-Methods=GET, POST, OPTIONS');
Add('Access-Control-Allow-Headers=Content-Type');
end;
end;
procedure HandleWithCORS(const Context: TWebContext);
begin
EnableCORS(Context);
if Context.Request.Method = 'OPTIONS' then
begin
// Preflight request
Context.Response.ResponseNo := 200;
Exit;
end;
// Handle actual request
HandleDataRequest(Context);
end;
Complete Web Server Example
uses
BI.DataItem, BI.Persist, BI.SQL, BI.JSON,
BI.Web.Server.Indy, BI.Web.Context,
System.SysUtils, System.Classes;
type
TBIWebServer = class(TForm)
MemoLog: TMemo;
BtnStart: TButton;
BtnStop: TButton;
EditPort: TEdit;
LabelStatus: TLabel;
LabelConnections: TLabel;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure BtnStartClick(Sender: TObject);
procedure BtnStopClick(Sender: TObject);
private
Server: THttpServer;
Data: TDataItem;
procedure LoadData;
procedure HandleRequest(const Context: TWebContext);
procedure HandleConnect(const Context: TWebContext);
procedure HandleDisconnect(const Context: TWebContext);
procedure HandleException(const Context: TWebContext; const E: Exception);
procedure UpdateStatus;
procedure ServeProductList(const Context: TWebContext);
procedure ServeProductQuery(const Context: TWebContext);
procedure ServeStatistics(const Context: TWebContext);
end;
procedure TBIWebServer.FormCreate(Sender: TObject);
begin
EditPort.Text := '15015';
LoadData;
end;
procedure TBIWebServer.LoadData;
begin
Data := TStore.Load('BISamples', 'Products');
MemoLog.Lines.Add('Data loaded: ' + IntToStr(Data.Count) + ' products');
end;
procedure TBIWebServer.BtnStartClick(Sender: TObject);
begin
if Server <> nil then Exit;
try
Server := THttpServer.Create(Self);
Server.Port := StrToInt(EditPort.Text);
Server.OnCommandGet := HandleRequest;
Server.OnConnect := HandleConnect;
Server.OnDisconnect := HandleDisconnect;
Server.OnException := HandleException;
Server.Active := True;
UpdateStatus;
MemoLog.Lines.Add('Server started on port ' + EditPort.Text);
MemoLog.Lines.Add('Try: http://localhost:' + EditPort.Text + '/products');
except
on E: Exception do
begin
MemoLog.Lines.Add('Error starting server: ' + E.Message);
Server.Free;
Server := nil;
end;
end;
end;
procedure TBIWebServer.BtnStopClick(Sender: TObject);
begin
if Server = nil then Exit;
Server.Active := False;
Server.Free;
Server := nil;
UpdateStatus;
MemoLog.Lines.Add('Server stopped');
end;
procedure TBIWebServer.HandleRequest(const Context: TWebContext);
var
Path: String;
begin
Path := Context.Request.Document;
MemoLog.Lines.Add('Request: ' + Path);
// Enable CORS
with Context.Response.CustomHeaders do
begin
Add('Access-Control-Allow-Origin=*');
Add('Access-Control-Allow-Methods=GET');
end;
// Route to handlers
if Path = '/products' then
ServeProductList(Context)
else if StartsText('/query', Path) then
ServeProductQuery(Context)
else if Path = '/stats' then
ServeStatistics(Context)
else
begin
Context.Response.ContentText :=
'{"error": "Not Found", "path": "' + Path + '"}';
Context.Response.ContentType := 'application/json';
Context.Response.ResponseNo := 404;
end;
end;
procedure TBIWebServer.ServeProductList(const Context: TWebContext);
var
JSON: String;
begin
JSON := TBIJSONExport.AsString(Data);
Context.Response.ContentText := JSON;
Context.Response.ContentType := 'application/json';
end;
procedure TBIWebServer.ServeProductQuery(const Context: TWebContext);
var
Category, SQL, JSON: String;
Result: TDataItem;
begin
// GET /query?category=Electronics
Category := Context.Request.Params.Values['category'];
if Category <> '' then
SQL := 'ProductName, UnitPrice, Stock where Category = "' + Category + '"'
else
SQL := 'ProductName, UnitPrice, Stock';
Result := TBISQL.From(Data, SQL);
try
JSON := TBIJSONExport.AsString(Result);
Context.Response.ContentText := JSON;
Context.Response.ContentType := 'application/json';
finally
Result.Free;
end;
end;
procedure TBIWebServer.ServeStatistics(const Context: TWebContext);
var
Result: TDataItem;
JSON: String;
begin
// Return aggregated statistics
Result := TBISQL.From(Data,
'Category, count(*) as Count, sum(Stock) as TotalStock, ' +
'avg(UnitPrice) as AvgPrice group by Category');
try
JSON := TBIJSONExport.AsString(Result);
Context.Response.ContentText := JSON;
Context.Response.ContentType := 'application/json';
finally
Result.Free;
end;
end;
procedure TBIWebServer.HandleConnect(const Context: TWebContext);
begin
UpdateStatus;
end;
procedure TBIWebServer.HandleDisconnect(const Context: TWebContext);
begin
UpdateStatus;
end;
procedure TBIWebServer.HandleException(const Context: TWebContext;
const E: Exception);
begin
MemoLog.Lines.Add('Exception: ' + E.Message);
Context.Response.ContentText :=
'{"error": "' + E.Message + '"}';
Context.Response.ContentType := 'application/json';
Context.Response.ResponseNo := 500;
end;
procedure TBIWebServer.UpdateStatus;
begin
if Server <> nil then
begin
LabelStatus.Caption := 'Running';
LabelConnections.Caption := 'Connections: ' +
IntToStr(Server.ContextsCount);
BtnStart.Enabled := False;
BtnStop.Enabled := True;
end
else
begin
LabelStatus.Caption := 'Stopped';
LabelConnections.Caption := 'Connections: 0';
BtnStart.Enabled := True;
BtnStop.Enabled := False;
end;
end;
procedure TBIWebServer.FormDestroy(Sender: TObject);
begin
if Server <> nil then
begin
Server.Active := False;
Server.Free;
end;
Data.Free;
end;
Client-Side Access
JavaScript Client
// Fetch data from TeeBI server
async function fetchProducts() {
const response = await fetch('http://localhost:15015/products');
const data = await response.json();
console.log(data);
}
// Query with parameters
async function queryByCategory(category) {
const response = await fetch(
`http://localhost:15015/query?category=${category}`
);
const data = await response.json();
return data;
}
// Get statistics
async function getStats() {
const response = await fetch('http://localhost:15015/stats');
const data = await response.json();
return data;
}
Delphi Client
uses
BI.DataSource, BI.JSON;
// Load data from remote server
var Data: TDataItem;
begin
Data := TBIURLSource.From('http://server:15015/products');
try
BIGrid1.Data := Data;
finally
// Data owned by grid
end;
end;
Performance Tips
Binary Format
Use TeeBI binary format for 10x faster transfers than JSON
Compression
Enable compression for large responses:
Context.Response.Compress := True;
Caching
Cache frequently accessed data in memory
Connection Pooling
Reuse data connections across requests
Security Considerations
The basic web server is for internal/development use. For production:
- Implement authentication (JWT, OAuth)
- Use HTTPS/TLS encryption
- Validate and sanitize all inputs
- Implement rate limiting
- Use a reverse proxy (nginx, Apache)
See Also
- Importing Data - Load data from URLs
- SQL Queries - Process web requests
- Big Data - Serve large datasets
- Parallel Processing - Handle concurrent requests
