Indy HTTP Server 使用 OpenSSL 3.0

前言

使用 Indy HTTP Server 做一个 Web Server 或者一个 WebService Server,如果使用 Delphi 默认的 TIdServerIOHandlerSSLOpenSSL,只能加载 OpenSSL 1.0 的 DLL 库。

现如今 OpenSSL 已经是 3.0 了。

还好网上有开源的 TTaurusTLSServerIOHandler 可以用来替换 TIdServerIOHandlerSSLOpenSSL 加载 OpenSSL 3.0 的 DLL 库。

以下描述,基于 Delphi 12 社区版。

如何使用

OpenSSL 1.x 的库

首先,如果使用 TIdServerIOHandlerSSLOpenSSL 加载 OpenSSL 1.0 的动态链接库,应该给程序准备:

1**. libeay32.dll**

2.ssleay32.dll

上述两个文件的下载地址,请参考:

Securing Indy Network Connections - RAD Studio

上面那篇官方文档里面提到的下载地址是:

Index of /SSL

OpenSSL 3.x 的库

如果使用 OpenSSL 3.0 的动态链接库,则应该是给程序以下两个文件:

  1. libcrypto-3.dll

  2. libssl-3.dll

上述两个 OpenSSL 3.x 的文件的下载地址,在:

https://github.com/JPeterMugaas/TaurusTLS/tree/main

这个地址的首页里面有提供。

关于 TaurusTLS 的使用

首先去 https://github.com/JPeterMugaas/TaurusTLS/tree/main 下载这个控件的源代码。

然后在 Delphi 里面安装它。

问题:

它的官方页面里面提到:

  1. Set the INDY_PATH environment variable for your user account to the location where Indy is located.
  2. Open TaurusForIndy290All.groupproj in the TaurusTLS\Packages\d12 folder.
  3. Compile TaurusTLS_RTForIndy290.
  4. Compile TaurusTLS_DTForIndy290 and install it in the IDE.

但实际上,按照上述说法,没法安装。

问题的详情

TaurusTLS_RTForIndy290 的源文件里面:

Delphi 复制代码
requires
  rtl,
  IndyCore290,
  IndySystem290,
  IndyProtocols290;

实际上 Delphi 自带的 Indy 里面,没有 IndyCore290.dcp 等上述三个带 290 的文件。

把上述代码的 290 删除掉,仅仅留下 IndyCore 以及另外两个同样去掉 290 的引用,编译能够通过,但整个硬盘到处找,也找不到编译后的 bpl 文件。

问题的解决

仔细观察这个控件所在的文件夹:

Delphi\Controls\TaurusTLS-main\Packages\d12

发现里面实际上有两个包文件,名字是:

TaurusTLS_RT.dpk

TaurusTLS_DT.dpk

打开这两个包文件来编译,安装控件成功!

因此,不应该按照它的说明,编译 TaurusTLS_DTForIndy290.dpk 和 TaurusTLS_RTForIndy290.dpk 这两个项目。

使用注意

在 Delphi 中创建一个 Soap Server 项目,选择 Stand alone 模式,因此它就自带了 Indy HTTP Server 作为内置的 Web Server。创建项目时,那个 https 的检查框不要勾选。

程序有了以后,顺便创建一个 WebService 的接口。给接口搞一个函数。我这里是:

Delphi 复制代码
{ Invokable interface IIndySSLTaurus }

unit IndySSLTaurusIntf;

interface

uses Soap.InvokeRegistry, System.Types, Soap.XSBuiltIns;

type

  { Invokable interfaces must derive from IInvokable }
  IIndySSLTaurus = interface(IInvokable)
  ['{CA6E9719-4ABF-4E22-8E81-C74231025E99}']

    { Methods of Invokable interface must not use the default }
    { calling convention; stdcall is recommended }
    function Hello(const S: string): string; stdcall;
  end;

implementation

initialization
  { Invokable interfaces must be registered }
  InvRegistry.RegisterInterface(TypeInfo(IIndySSLTaurus));

end.

上述接口的实现部分的代码:

Delphi 复制代码
{ Invokable implementation File for TIndySSLTaurus which implements IIndySSLTaurus }

unit IndySSLTaurusImpl;

interface

uses Soap.InvokeRegistry, System.Types, Soap.XSBuiltIns, IndySSLTaurusIntf;

type

  { TIndySSLTaurus }
  TIndySSLTaurus = class(TInvokableClass, IIndySSLTaurus)
  public
    function Hello(const S: string): string; stdcall;
  end;

implementation


{ TIndySSLTaurus }

function TIndySSLTaurus.Hello(const S: string): string;
begin
  Result := 'Hello, ' + S;
end;

initialization
{ Invokable classes must be registered }
   InvRegistry.RegisterInvokableClass(TIndySSLTaurus);
end.

这个项目,Delphi IDE 默认给出的是 8080 端口。运行它。

然后,做一个 WebService 客户端程序,拖一个 HTTPRIO1 这个控件到 Form 上面。

这个时候,还没有实现 **https,**因此,给这个 HTTPRIO1.URL 赋值:

http://localhost:8080/soap 就可以了。

给客户端写测试代码:

Delphi 复制代码
procedure TForm2.Button1Click(Sender: TObject);
var
  Intf: IIndySSLTaurus;
begin
  Intf := HTTPRIO1 as IIndySSLTaurus;
  try
    Memo1.Lines.Add(Intf.Hello(Edit1.Text));
  finally
    Intf := nil;
  end;
end;

运行客户端,点击按钮,能够在 Memo1 里面看到从服务器端正确返回的值。测试通过。

为服务器端增加 OpenSSL 3.x 的支持

从控件面板,拖一个 TaurusTLSServerIOHandler1 到主界面上。然后为服务器端增加以下代码:

Delphi 复制代码
procedure TForm1.FormCreate(Sender: TObject);
begin
  FServer := TIdHTTPWebBrokerBridge.Create(Self);

  //必须用代码指定。设计期在属性面板里面指定的证书文件,没有效果。
  TaurusTLSServerIOHandler1.DefaultCert.PublicKey := 'mysite.net.cert.pem';
  TaurusTLSServerIOHandler1.DefaultCert.PrivateKey := 'mysite.net.key.pem';
  FServer.OnQuerySSLPort := OnQuerySSLPort;

    //写绝对路径也没问题
//  TaurusTLSServerIOHandler1.DefaultCert.PublicKey := 'D:\TestD12\IndySSL_Taurus\证书备份\mysite.net.cert.pem';
//  TaurusTLSServerIOHandler1.DefaultCert.PrivateKey := 'D:\TestD12\IndySSL_Taurus\证书备份\mysite.net.key.pem';

  FServer.IOHandler := TaurusTLSServerIOHandler1; 

  TaurusTLS.LoadOpenSSLLibrary;
end;

procedure TForm1.OnQuerySSLPort(APort: TIdPort; var AUseSSL: Boolean);
begin
  APort := 8080;   //如果不加上这个,就只能走默认的 443
  AUseSSL := True;
end;

剩下的代码就是 Delphi IDE 在创建这个 Soap Server 的时候自动创建的代码了,如下:

Delphi 复制代码
procedure TForm1.StartServer;
begin
  if not FServer.Active then
  begin
    FServer.Bindings.Clear;
    FServer.DefaultPort := StrToInt(EditPort.Text); //这里默认是 8080
    FServer.OnQuerySSLPort := OnQuerySSLPort;
    FServer.Active := True;
  end;
end;

另外,还有个事件方法,是关于证书的密码的:

Delphi 复制代码
procedure TForm1.TaurusTLSServerIOHandler1GetPassword(ASender: TObject;
  var VPassword: string; const AIsWrite: Boolean; var VOk: Boolean);
begin
  VPassword := '';
end;

我的证书是自签发证书,没有密码。因此这个事件方法没有也能运行。

到这里,这个支持OpenSSL 3.x 的 https 的 Soap Server 就可以使用了。编译运行它。

把前面的客户端的 URL 改为:https://localhost:8080/soap 然后运行客户端,测试通过。

需要注意的问题

我在前面的代码里面,为这个控件,赋值了证书文件名:

Delphi 复制代码
TaurusTLSServerIOHandler1.DefaultCert.PublicKey := 'mysite.net.cert.pem';
  TaurusTLSServerIOHandler1.DefaultCert.PrivateKey := 'mysite.net.key.pem';

实际上,它在设计期的属性面板里面,有这两个属性。

但是,如果我们在设计期的属性面板里面填写上面的两个文件名,不管是填文件名,还是填写带绝对路径的文件名,只要没有用代码为它赋值,都不能正常工作。

对比起来,如果采用 Indy 自己的 IdServerIOHandlerSSLOpenSSL1(只支持 OpenSSL 1.x)的话,设计期填写的上述属性,也是能够正常工作的。

还有一个小问题

因为我使用的是自己签发的证书,所以在测试中,在 Debug 状态下,用浏览器访问本程序,Delphi IDE 会弹出不少异常提示,然后浏览器还是能够显示这个 Soap Server 的首页。但此时如果点击首页上的其它链接,比如查看接口,就会出错到页面完全无法显示。

但是,如果此时是使用 Delphi 自己的 Web Service 客户端去访问这个服务器,调用接口函数,没有任何问题,也没有异常错误提示。

结束

使用 Taurus这个控件,确实能够让 Indy 调用 OpenSSL 3.x 的 DLL 库。