Raffaele Rossi
Raffaele Rossi

Reputation: 3127

Delphi IdHTTP server load html

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:

  1. Drop a memo in the form
  2. Add the html inside the memo
  3. Load the text of the memo in the 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.

enter image description here

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:

  1. Do I have to store the html pages somewhere in a folder and then load them using indy?
  2. Can I load html pages with indy that are included in the Deployment page?

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

Answers (2)

Remy Lebeau
Remy Lebeau

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;
  1. Do I have to store the html pages somewhere in a folder and then load them using indy?

  2. 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:

  1. 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;
    
  2. 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

Kohull
Kohull

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

Related Questions