Reputation: 3127
I have created a VCL application and I need to create an HTTP server that runs in my network. I have created the code that you can see below:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
a: TStringList;
count, logN: integer;
begin
if ARequestInfo.Document = '/' then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentText := IndexMemo.Lines.Text;
Memo1.Lines.Add(' Client: ' + ARequestInfo.RemoteIP);
end
else
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentText := '<html><body><b>404 NOT FOUND</b></body></html>';
end;
end;
Now I have only a test case if ARequestInfo.Document = '/' then
but later I'll need a lot of them. I have found this solution:
ContextText
I don't think that this is very efficient because I'd have to drop like 20 TMemo in my form and the HTML will be difficult to maintain. I thought that I could load the html pages with the Deployment manager
.
In the same folder of the Delphi project I have created a folder called pages
and it will contain the html files. I am not sure on how to load html pages with an indy HTTP server, so my questions are:
Note: I would like to have a single exe (which is the http server) and not a folder with exe + html files. The solution that I have found works pretty well because I use a lot of TMemo
to store the code, but this is not easy to maintain.
Upvotes: 1
Views: 4364
Reputation: 595295
First, the code you have shown is not thread-safe. TIdHTTPServer
is a multi-threaded component, the OnCommand...
events are triggered in the context of worker threads. You must synchronize with the main UI thread in order to access UI controls safely, eg:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
s: string;
begin
if ARequestInfo.Document = '/' then
begin
TThread.Synchronize(nil,
procedure
begin
s := IndexMemo.Lines.Text;
Memo1.Lines.Add(' Client: ' + ARequestInfo.RemoteIP);
end
);
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentText := s;
AResponseInfo.ContentType := 'text/plain';
end
else
begin
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentText := '<html><body><b>404 NOT FOUND</b></body></html>';
AResponseInfo.ContentType := 'text/html';
end;
end;
Do I have to store the html pages somewhere in a folder and then load them using indy?
Can I load html pages with indy that are included in the Deployment page?
If you want to serve files from the local filesystem, you have to translate the ARequestInfo.Document
property value to a local file path, and then you can either:
load the requested file into a TFileStream
and assign it to the AResponseInfo.ContentStream
property:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
str, filename: string;
begin
str := ' Client: ' + ARequestInfo.RemoteIP + ' requesting: ' + ARequestInfo.Document;
TThread.Queue(nil,
procedure
begin
Memo1.Lines.Add(str);
end
);
if TextStartsWith(ARequestInfo.Document, '/') then
begin
filename := Copy(ARequestInfo.Document, 2, MaxInt);
if filename = '' then
filename := 'index.txt';
// determine local path to requested file
// (ProcessPath() is declared in the IdGlobalProtocols unit)...
filename := ProcessPath(YourDeploymentFolder, filename);
if FileExists(filename) then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentStream := TFileStream.Create(filename, fmOpenRead or fmShareDenyWrite);
AResponseInfo.ContentType := IdHTTPServer1.MIMETable.GetFileMIMEType(filename);
Exit;
end;
end;
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentText := '<html><body><b>404 NOT FOUND</b></body></html>';
AResponseInfo.ContentType := 'text/html';
end;
pass the file path to the TIdHTTPResponseInfo.(Smart)ServeFile()
method and let it handle the file for you:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
str, filename: string;
begin
str := ' Client: ' + ARequestInfo.RemoteIP + ' requesting: ' + ARequestInfo.Document;
TThread.Queue(nil,
procedure
begin
Memo1.Lines.Add(str);
end
);
if TextStartsWith(ARequestInfo.Document, '/') then
begin
filename := Copy(ARequestInfo.Document, 2, MaxInt);
if filename = '' then
filename := 'index.txt';
// determine local path to requested file...
filename := ProcessPath(YourDeploymentFolder, filename);
AResponseInfo.SmartServeFile(AContext, ARequestInfo, filename);
Exit;
end;
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentText := '<html><body><b>404 NOT FOUND</b></body></html>';
AResponseInfo.ContentType := 'text/html';
end;
I would like to have a single exe (which is the http server) and not a folder with exe + html files.
In that case, save the HTML files into the EXE's resources at compile-time (using an .rc
file, or the IDE's Resources and Images dialog. See Resource Files Support for more details) and then translate the ARequestInfo.Document
into a resource ID/Name that you can load with TResourceStream
for use as the AResponseInfo.ContentStream
object:
procedure TForm1.IdHTTPServer1CommandGet(AContext: TIdContext;
ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var
str, resID: string;
strm: TResourceStream;
begin
str := ' Client: ' + ARequestInfo.RemoteIP + ' requesting: ' + ARequestInfo.Document;
TThread.Queue(nil,
procedure
begin
Memo1.Lines.Add(str);
end
);
if TextStartsWith(ARequestInfo.Document, '/') then
begin
// determine resource ID for requested file
// (you have to write this yourself)...
resID := TranslateIntoResourceID(Copy(ARequestInfo.Document, 2, MaxInt));
try
strm := TResourceStream.Create(HInstance, resID, RT_RCDATA);
except
on E: EResNotFound do
strm := nil;
end;
if strm <> nil then
begin
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentStream := strm;
AResponseInfo.ContentType := 'text/html';
Exit;
end;
end;
AResponseInfo.ResponseNo := 404;
AResponseInfo.ContentText := '<html><body><b>404 NOT FOUND</b></body></html>';
AResponseInfo.ContentType := 'text/html';
end;
Upvotes: 6
Reputation: 684
You can read the Content from a file
procedure TForm2.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var Page : TStringStream;
begin
Page := TStringStream.Create;
Page.LoadFromFile('put the file path here');
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentStream := page;
end;
You can read the Content from a Resource, go to Project Menu, Resources and Imagens, add the resources that you need.
procedure TForm2.IdHTTPServer1CommandGet(AContext: TIdContext; ARequestInfo: TIdHTTPRequestInfo; AResponseInfo: TIdHTTPResponseInfo);
var page : TResourceStream;
begin
//home is the resource name
page := TResourceStream.Create(HInstance, 'home', RT_RCDATA);
AResponseInfo.ResponseNo := 200;
AResponseInfo.ContentStream := page;
end;
Upvotes: 4