【问题标题】:Delphi IdHTTP server load htmlDelphi IdHTTP 服务器加载 html
【发布时间】:2017-05-03 12:37:27
【问题描述】:

我创建了一个 VCL 应用程序,我需要创建一个在我的网络中运行的 HTTP 服务器。我创建了您可以在下面看到的代码:

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;

现在我只有一个测试用例if ARequestInfo.Document = '/' then,但以后我会需要很多。我找到了这个解决方案:

  1. 在表单中添加备忘录
  2. 在备忘录中添加html
  3. ContextText中加载备忘录的文本

我认为这不是很有效,因为我必须在我的表单中删除大约 20 个 TMemo 并且 HTML 将难以维护。我以为我可以用Deployment manager 加载html页面。

在 Delphi 项目的同一个文件夹中,我创建了一个名为 pages 的文件夹,它将包含 html 文件。我不确定如何使用 indy HTTP 服务器加载 html 页面,所以我的问题是:

  1. 是否必须将 html 页面存储在文件夹中的某个位置,然后使用 indy 加载它们?
  2. 我可以使用 indy 加载部署页面中包含的 html 页面吗?

注意:我想要一个 exe(即 http 服务器)而不是包含 exe + html 文件的文件夹。我找到的解决方案效果很好,因为我使用了很多TMemo 来存储代码,但这并不容易维护。

【问题讨论】:

    标签: delphi indy


    【解决方案1】:

    首先,您显示的代码不是线程安全的。 TIdHTTPServer 是一个多线程组件,OnCommand... 事件在工作线程的上下文中触发。您必须与主 UI 线程同步才能安全地访问 UI 控件,例如:

    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. 我是否必须将 html 页面存储在文件夹中的某个位置,然后使用 indy 加载它们?

    2. 我可以用 indy 加载部署页面中包含的 html 页面吗?

    如果您想从本地文件系统提供文件,您必须将 ARequestInfo.Document 属性值转换为本地文件路径,然后您可以:

    1. 将请求的文件加载到TFileStream 并将其分配给AResponseInfo.ContentStream 属性:

      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. 将文件路径传递给TIdHTTPResponseInfo.(Smart)ServeFile() 方法,让它为您处理文件:

      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;
      

    我想要一个 exe(即 http 服务器)而不是包含 exe + html 文件的文件夹。

    在这种情况下,在编译时将 HTML 文件保存到 EXE 的资源中(使用 .rc 文件,或 IDE 的 Resources and Images 对话框。有关详细信息,请参阅 Resource Files Support),然后翻译 ARequestInfo.Document放入可以使用 TResourceStream 加载以用作 AResponseInfo.ContentStream 对象的资源 ID/名称:

    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;
    

    【讨论】:

    • 永远是最好的,你在这里解决了一个大问题。我没有想到它在单独的线程上运行的事实。非常感谢
    • TranslateIntoResourceID 来自哪里?与 TextStartsWith 相同,但我使用 if ARequestInfo.Document.Chars[0] = '/' then 快速解决了
    • "TranslateIntoResourceID 来自哪里?" - 你必须自己写。 “TextStartsWith 也一样” - 在 Indy 的 IdGlobal 单元中。
    【解决方案2】:

    您可以从文件中读取内容

    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;
    

    您可以从资源中读取内容,进入项目菜单、资源和图像,添加您需要的资源。

    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;
    

    【讨论】:

    • TIdHTTPResponseInfo(Smart)ServeFile() 方法可用,您不需要自己打开和读取文件。
    • 您应该使用TFileStream(或Indy 的TIdReadFileExclusiveStreamTIdReadFileNonExclusiveStream)而不是TStringStream。根本不需要将文件加载到内存中(当然不是作为字符串)。在这两个示例中,也不要忘记设置AResponseInfo.ContentType
    猜你喜欢
    • 1970-01-01
    • 2016-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多