Indy HTTPS POST Delphi 请求使用 TLS 1.0 而不是 TLS 1.2
Indy HTTPS POST Request in Delphi using TLS 1.0 instead of TLS 1.2
我正在尝试在 Delphi XE6 和 Indy (v10.6.2.0) 中发出 HTTPS Post 请求。
我能够成功执行对此 API 的调用并使用 JavaScript:
获得响应
但我需要在 Delphi 应用程序中执行相同的操作。
我使用 TIdSSLIOHandlerSocketOpenSSL
的各种配置进行了多次尝试,但出现错误:
Could not load SSL library
libeay32.dll
是罪魁祸首。我还访问了 Indy 的 GitHub 存储库,但不确定我需要下载哪个依赖文件。
下面的代码给我错误:
Cannot open file "C:\Users.. \IndyHTTPSTest\Win32\Debug\dXNlcm5hbWU6cGFzc3dvcmQ=". The system cannot find the file specified.
我缺少什么才能像 JavaScript 代码一样在 Delphi XE6 中将其添加到 运行?
我已将 libeay32.dll
和 ssleay32.dll
包含在我的项目文件夹中。
这是我的代码:
unit uMainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent, IdSSLOpenSSL, IdSSLOpenSSLHeaders,
System.NetEncoding, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;
type
TMainForm = class(TForm)
IdHTTP1: TIdHTTP;
GetBtn: TButton;
ResponseMemo: TMemo;
procedure GetBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
ss: TStringStream;
s, sLog, sFile : String;
Base64: TBase64Encoding;
Txt: TextFile;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
// Assign JSON to TStringStream
ss := TStringStream.Create('locations= [{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]', TEncoding.UTF8);
// Base64 encode username and password string to satisfy API for "Authorization", "Basic " + sLog);
s := 'username:password';
Base64 := TBase64Encoding.Create(20, '');
sLog := Base64.Encode(s);
end;
procedure TMainForm.GetBtnClick(Sender: TObject);
var
responseFromServer, sWhichFail, sVer :string;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self);
with LHandler do
begin
SSLOptions.Method := sslvSSLv2;
SSLOptions.Mode := sslmUnassigned;
SSLOptions.VerifyMode := [];
SSLOptions.VerifyDepth := 0;
host := '';
end;
IdHTTP1 := TIdHTTP.Create(Self);
with IdHTTP1 do
begin
IOHandler := LHandler;
AllowCookies := True;
ProxyParams.BasicAuthentication := False;
ProxyParams.ProxyPort := 0;
Request.ContentLength := -1;
Request.ContentType := 'application/x-www-form-urlencoded';
Request.ContentRangeEnd := 0;
Request.ContentRangeStart := 0;
Request.Accept := 'text/json, */*';
Request.BasicAuthentication := True;
Request.UserAgent := 'Mozilla/3.0 (compatible; Indy Library)';
HTTPOptions := [hoForceEncodeParams];
end;
try
// Set up the request and send it
sFile:= IdHTTP1.Post('https://api.routexl.com/tour', sLog);
ResponseMemo.Lines.Add(Format('Response Code: %d', [IdHTTP1.ResponseCode]));
ResponseMemo.Lines.Add(Format('Response Text: %s', [IdHTTP1.ResponseText]));
finally
sWhichFail:= WhichFailedToLoad();
ShowMessage(sWhichFail);
ResponseMemo.Lines.Text:= sWhichFail;
//sVer := OpenSSLVersion();
//ShowMessage(sVer);
end;
end;
end.
更新:我更新了代码,并添加了屏幕截图以帮助调试:
unit uMainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent, IdSSLOpenSSL, IdSSLOpenSSLHeaders,
System.NetEncoding, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;
type
TMainForm = class(TForm)
IdHTTP1: TIdHTTP;
GetBtn: TButton;
ResponseMemo: TMemo;
procedure GetBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
sl: TStringList;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
sl := TStringList.Create;
sl.Add('locations=[{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]');
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
sl.Free;
end;
procedure TMainForm.GetBtnClick(Sender: TObject);
var
LHTTP: TIdHTTP;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
responseFromServer, sWhichFail, sVer :string;
begin
ResponseMemo.Clear;
LHTTP := TIdHTTP.Create(nil);
try
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(LHTTP);
LHandler.SSLOptions.SSLVersions := [sslvTLSv1_2];
LHandler.SSLOptions.Mode := sslmClient;
LHandler.SSLOptions.VerifyMode := [];
LHandler.SSLOptions.VerifyDepth := 0;
LHTTP.IOHandler := LHandler;
LHTTP.AllowCookies := True;
LHTTP.Request.Accept := 'text/json, */*';
LHTTP.Request.BasicAuthentication := True;
LHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
LHTTP.Request.Username := 'username:';
LHTTP.Request.Password := 'password';
LHTTP.HTTPOptions := [hoForceEncodeParams];
try
responseFromServer := IdHTTP1.Post('https://api.routexl.com/tour', sl);
ResponseMemo.Lines.Text := responseFromServer;
except
on E: EIdOSSLCouldNotLoadSSLLibrary do
begin
sVer := OpenSSLVersion();
sWhichFail := WhichFailedToLoad();
ResponseMemo.Lines.Add(sVer);
ResponseMemo.Lines.Add(sWhichFail);
ShowMessage('Could not load OpenSSL');
end;
on E: EIdHTTPProtocolException do
begin
ResponseMemo.Lines.Add(Format('Response Code: %d', [LHTTP.ResponseCode]));
ResponseMemo.Lines.Add(Format('Response Text: %s', [LHTTP.ResponseText]));
ResponseMemo.Lines.Add(E.ErrorMessage);
ShowMessage('HTTP Failure');
end;
on E: Exception do
begin
ResponseMemo.Lines.Add(E.ClassName);
ResponseMemo.Lines.Add(E.Message);
ShowMessage('Unknown Error');
end;
end;
finally
LHTTP.Free;
end;
end;
end.
WireShark 在执行 JavaScript 代码时显示以下内容:
WireShark 在执行 Delphi 代码时显示以下内容:
您可以将 sLog: String
的声明更改为 sLog: TStrings
。然后在 onCreate
实例化 sLog := TStringList.Create
然后在代码 sLog.Text := Base64.Encode(s);
.
所以,
var
MainForm: TMainForm;
ss: TStringStream;
s, sFile : String;
sLog: TStrings;
Base64: TBase64Encoding;
Txt: TextFile;
procedure TMainForm.FormCreate(Sender: TObject);
begin
sLog := TStringList.Create;
// Assign JSON to TStringStream
ss := TStringStream.Create('locations= [{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]', TEncoding.UTF8);
// Base64 encode username and password string to satisfy API for "Authorization", "Basic " + sLog);
s := 'username:password';
Base64 := TBase64Encoding.Create(20, '');
sLog.Text := Base64.Encode(s);
end;
当您从 Indy 收到 OpenSSL 加载错误时,您可以在 IdSSLOpenSSLHeaders
单元中调用 Indy 的 WhichFailedToLoad()
函数来找出原因。你是谁,但你没有向我们展示它实际上在说什么,所以我们无法诊断为什么 OpenSSL 无法加载。通常,加载会失败,因为:
libeay32.dll
and/or ssleay32.dll
找不到。如果它们与您的程序不在同一个文件夹中,或者至少不在 OS' DLL 搜索路径中,那么您可以在 IdSSLOpenSSLHeaders
单元中使用 Indy 的 IdOpenSSLSetLibPath()
函数来告诉 Indy 在哪里寻找他们。
它们的位数与您的程序不同。 32 位程序无法加载 64 位 DLL,反之亦然。
它们缺少必需的导出函数,这表明它们可能是 Indy 根本不支持的 OpenSSL 版本。例如,TIdSSLIOHandlerSocketOpenSSL
最多只支持 OpenSSL 1.0.2u。对于 OpenSSL 1.1.x+,您需要使用 this experimental SSLIOHandler
代替。
话虽如此,我发现您的 Post 代码有几个问题:
您正在手动编码根本不需要手动编码的输入数据。 TIdHTTP.Post()
可以在内部为您处理。
您正在调用 TIdHTTP.Post()
的版本,该版本将 String
文件名作为输入,它将打开指定的文件并按原样发送其内容。但是您没有传递文件名,而是传递了 Base64 编码的 用户凭据 (甚至不是您的 JSON)。要制作正确的 application/x-www-webform-urlencoded
post,您需要使用 TIdHTTP.Post()
版本,该版本采用 TStrings
对 name=value
作为输入。
TIdHTTP
本机实现 Basic
身份验证,因此您应该使用 TIdHTTP.Request.UserName
和 TIdHTTP.Request.Password
属性作为您的用户凭据。
您告诉 TIdSSLIOHandlerSocketOpenSSL
使用 SSL v2.0。没有人再使用它,因为它不再安全。您不应使用低于 TLS v1.0 的任何东西作为绝对最低限度。甚至在大多数服务器上也正在逐步淘汰。因此,请确保您也启用了 TLS v1.1 和 TLS v1.1(对于 TLS v1.3,您需要使用 other SSLIOHandler
)。
坦率地说,您的 Delphi 代码甚至没有接近复制 Javascript 代码所做的事情。试试这样的东西:
unit uMainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent, IdSSLOpenSSL, IdSSLOpenSSLHeaders,
System.NetEncoding, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;
type
TMainForm = class(TForm)
GetBtn: TButton;
ResponseMemo: TMemo;
procedure GetBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
sl: TStringList;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
sl := TStringList.Create;
sl.Add('locations=[{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]');
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
sl.Free;
end;
procedure TMainForm.GetBtnClick(Sender: TObject);
var
LHTTP: TIdHTTP;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
responseFromServer, sWhichFail, sVer :string;
begin
ResponseMemo.Clear;
LHTTP := TIdHTTP.Create(nil);
try
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(LHTTP);
LHandler.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
LHandler.SSLOptions.Mode := sslmClient;
LHandler.SSLOptions.VerifyMode := [];
LHandler.SSLOptions.VerifyDepth := 0;
LHTTP.IOHandler := LHandler;
LHTTP.AllowCookies := True;
LHTTP.Request.Accept := 'text/json, */*';
LHTTP.Request.BasicAuthentication := True;
LHTTP.Request.Username := 'username';
LHTTP.Request.Password := 'password';
LHTTP.HTTPOptions := [hoForceEncodeParams];
try
responseFromServer := IdHTTP1.Post('https://api.routexl.com/tour', sl);
ResponseMemo.Lines.Text := responseFromServer;
except
on E: EIdOSSLCouldNotLoadSSLLibrary do
begin
sVer := OpenSSLVersion();
sWhichFail := WhichFailedToLoad();
ResponseMemo.Lines.Add(sVer);
ResponseMemo.Lines.Add(sWhichFail);
ShowMessage('Could not load OpenSSL');
end;
on E: EIdHTTPProtocolException do
begin
ResponseMemo.Lines.Add(Format('Response Code: %d', [LHTTP.ResponseCode]));
ResponseMemo.Lines.Add(Format('Response Text: %s', [LHTTP.ResponseText]));
ResponseMemo.Lines.Add(E.ErrorMessage);
ShowMessage('HTTP Failure');
end;
on E: Exception do
begin
ResponseMemo.Lines.Add(E.ClassName);
ResponseMemo.Lines.Add(E.Message);
ShowMessage('Unknown Error');
end;
end;
finally
LHTTP.Free;
end;
end;
end.
UPDATE:也就是说,API documentation 显示了以下卷曲示例:
Get an unoptimized route using cURL:
curl \
--url https://api.routexl.com/tour/ \
--user <username>:<password> \
--data 'locations=[{"address":"1","lat":"52.05429","lng":"4.248618"},{"address":"2","lat":"52.076892","lng":"4.26975"},{"address":"3","lat":"51.669946","lng":"5.61852"},{"address":"4","lat":"51.589548","lng":"5.432482"}]&skipOptimisation=true'
,该示例确实转换为上述 Delphi 代码
-d, --data
(HTTP MQTT) Sends the specified data in a POST request to the HTTP server, in the same way that a browser does when a user has filled in an HTML form and presses the submit button. This will cause curl to pass the data to the server using the content-type application/x-www-form-urlencoded. Compare to -F, --form.
除一个细节外 - 当发送 application/x-www-form-urlencoded
请求并启用 hoForceEncodeParams
选项时,TIdHTTP.Post()
将对 []
、{}
、"
、:
和 ,
字符在 %HH
格式的 JSON 中。但是 GitHub 上的 API 的 Javascript and PHP examples 没有进行这种编码,他们按原样发送 JSON。因此,如果 API 服务器不喜欢该编码,则只需禁用 hoForceEncodeParams
选项:
LHTTP.HTTPOptions := [];
我正在尝试在 Delphi XE6 和 Indy (v10.6.2.0) 中发出 HTTPS Post 请求。
我能够成功执行对此 API 的调用并使用 JavaScript:
获得响应但我需要在 Delphi 应用程序中执行相同的操作。
我使用 TIdSSLIOHandlerSocketOpenSSL
的各种配置进行了多次尝试,但出现错误:
Could not load SSL library
libeay32.dll
是罪魁祸首。我还访问了 Indy 的 GitHub 存储库,但不确定我需要下载哪个依赖文件。
下面的代码给我错误:
Cannot open file "C:\Users.. \IndyHTTPSTest\Win32\Debug\dXNlcm5hbWU6cGFzc3dvcmQ=". The system cannot find the file specified.
我缺少什么才能像 JavaScript 代码一样在 Delphi XE6 中将其添加到 运行?
我已将 libeay32.dll
和 ssleay32.dll
包含在我的项目文件夹中。
这是我的代码:
unit uMainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent, IdSSLOpenSSL, IdSSLOpenSSLHeaders,
System.NetEncoding, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;
type
TMainForm = class(TForm)
IdHTTP1: TIdHTTP;
GetBtn: TButton;
ResponseMemo: TMemo;
procedure GetBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
MainForm: TMainForm;
ss: TStringStream;
s, sLog, sFile : String;
Base64: TBase64Encoding;
Txt: TextFile;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
// Assign JSON to TStringStream
ss := TStringStream.Create('locations= [{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]', TEncoding.UTF8);
// Base64 encode username and password string to satisfy API for "Authorization", "Basic " + sLog);
s := 'username:password';
Base64 := TBase64Encoding.Create(20, '');
sLog := Base64.Encode(s);
end;
procedure TMainForm.GetBtnClick(Sender: TObject);
var
responseFromServer, sWhichFail, sVer :string;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(self);
with LHandler do
begin
SSLOptions.Method := sslvSSLv2;
SSLOptions.Mode := sslmUnassigned;
SSLOptions.VerifyMode := [];
SSLOptions.VerifyDepth := 0;
host := '';
end;
IdHTTP1 := TIdHTTP.Create(Self);
with IdHTTP1 do
begin
IOHandler := LHandler;
AllowCookies := True;
ProxyParams.BasicAuthentication := False;
ProxyParams.ProxyPort := 0;
Request.ContentLength := -1;
Request.ContentType := 'application/x-www-form-urlencoded';
Request.ContentRangeEnd := 0;
Request.ContentRangeStart := 0;
Request.Accept := 'text/json, */*';
Request.BasicAuthentication := True;
Request.UserAgent := 'Mozilla/3.0 (compatible; Indy Library)';
HTTPOptions := [hoForceEncodeParams];
end;
try
// Set up the request and send it
sFile:= IdHTTP1.Post('https://api.routexl.com/tour', sLog);
ResponseMemo.Lines.Add(Format('Response Code: %d', [IdHTTP1.ResponseCode]));
ResponseMemo.Lines.Add(Format('Response Text: %s', [IdHTTP1.ResponseText]));
finally
sWhichFail:= WhichFailedToLoad();
ShowMessage(sWhichFail);
ResponseMemo.Lines.Text:= sWhichFail;
//sVer := OpenSSLVersion();
//ShowMessage(sVer);
end;
end;
end.
更新:我更新了代码,并添加了屏幕截图以帮助调试:
unit uMainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent, IdSSLOpenSSL, IdSSLOpenSSLHeaders,
System.NetEncoding, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;
type
TMainForm = class(TForm)
IdHTTP1: TIdHTTP;
GetBtn: TButton;
ResponseMemo: TMemo;
procedure GetBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
sl: TStringList;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
sl := TStringList.Create;
sl.Add('locations=[{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]');
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
sl.Free;
end;
procedure TMainForm.GetBtnClick(Sender: TObject);
var
LHTTP: TIdHTTP;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
responseFromServer, sWhichFail, sVer :string;
begin
ResponseMemo.Clear;
LHTTP := TIdHTTP.Create(nil);
try
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(LHTTP);
LHandler.SSLOptions.SSLVersions := [sslvTLSv1_2];
LHandler.SSLOptions.Mode := sslmClient;
LHandler.SSLOptions.VerifyMode := [];
LHandler.SSLOptions.VerifyDepth := 0;
LHTTP.IOHandler := LHandler;
LHTTP.AllowCookies := True;
LHTTP.Request.Accept := 'text/json, */*';
LHTTP.Request.BasicAuthentication := True;
LHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
LHTTP.Request.Username := 'username:';
LHTTP.Request.Password := 'password';
LHTTP.HTTPOptions := [hoForceEncodeParams];
try
responseFromServer := IdHTTP1.Post('https://api.routexl.com/tour', sl);
ResponseMemo.Lines.Text := responseFromServer;
except
on E: EIdOSSLCouldNotLoadSSLLibrary do
begin
sVer := OpenSSLVersion();
sWhichFail := WhichFailedToLoad();
ResponseMemo.Lines.Add(sVer);
ResponseMemo.Lines.Add(sWhichFail);
ShowMessage('Could not load OpenSSL');
end;
on E: EIdHTTPProtocolException do
begin
ResponseMemo.Lines.Add(Format('Response Code: %d', [LHTTP.ResponseCode]));
ResponseMemo.Lines.Add(Format('Response Text: %s', [LHTTP.ResponseText]));
ResponseMemo.Lines.Add(E.ErrorMessage);
ShowMessage('HTTP Failure');
end;
on E: Exception do
begin
ResponseMemo.Lines.Add(E.ClassName);
ResponseMemo.Lines.Add(E.Message);
ShowMessage('Unknown Error');
end;
end;
finally
LHTTP.Free;
end;
end;
end.
WireShark 在执行 JavaScript 代码时显示以下内容:
WireShark 在执行 Delphi 代码时显示以下内容:
您可以将 sLog: String
的声明更改为 sLog: TStrings
。然后在 onCreate
实例化 sLog := TStringList.Create
然后在代码 sLog.Text := Base64.Encode(s);
.
所以,
var
MainForm: TMainForm;
ss: TStringStream;
s, sFile : String;
sLog: TStrings;
Base64: TBase64Encoding;
Txt: TextFile;
procedure TMainForm.FormCreate(Sender: TObject);
begin
sLog := TStringList.Create;
// Assign JSON to TStringStream
ss := TStringStream.Create('locations= [{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]', TEncoding.UTF8);
// Base64 encode username and password string to satisfy API for "Authorization", "Basic " + sLog);
s := 'username:password';
Base64 := TBase64Encoding.Create(20, '');
sLog.Text := Base64.Encode(s);
end;
当您从 Indy 收到 OpenSSL 加载错误时,您可以在 IdSSLOpenSSLHeaders
单元中调用 Indy 的 WhichFailedToLoad()
函数来找出原因。你是谁,但你没有向我们展示它实际上在说什么,所以我们无法诊断为什么 OpenSSL 无法加载。通常,加载会失败,因为:
libeay32.dll
and/orssleay32.dll
找不到。如果它们与您的程序不在同一个文件夹中,或者至少不在 OS' DLL 搜索路径中,那么您可以在IdSSLOpenSSLHeaders
单元中使用 Indy 的IdOpenSSLSetLibPath()
函数来告诉 Indy 在哪里寻找他们。它们的位数与您的程序不同。 32 位程序无法加载 64 位 DLL,反之亦然。
它们缺少必需的导出函数,这表明它们可能是 Indy 根本不支持的 OpenSSL 版本。例如,
TIdSSLIOHandlerSocketOpenSSL
最多只支持 OpenSSL 1.0.2u。对于 OpenSSL 1.1.x+,您需要使用 this experimentalSSLIOHandler
代替。
话虽如此,我发现您的 Post 代码有几个问题:
您正在手动编码根本不需要手动编码的输入数据。
TIdHTTP.Post()
可以在内部为您处理。您正在调用
TIdHTTP.Post()
的版本,该版本将String
文件名作为输入,它将打开指定的文件并按原样发送其内容。但是您没有传递文件名,而是传递了 Base64 编码的 用户凭据 (甚至不是您的 JSON)。要制作正确的application/x-www-webform-urlencoded
post,您需要使用TIdHTTP.Post()
版本,该版本采用TStrings
对name=value
作为输入。TIdHTTP
本机实现Basic
身份验证,因此您应该使用TIdHTTP.Request.UserName
和TIdHTTP.Request.Password
属性作为您的用户凭据。您告诉
TIdSSLIOHandlerSocketOpenSSL
使用 SSL v2.0。没有人再使用它,因为它不再安全。您不应使用低于 TLS v1.0 的任何东西作为绝对最低限度。甚至在大多数服务器上也正在逐步淘汰。因此,请确保您也启用了 TLS v1.1 和 TLS v1.1(对于 TLS v1.3,您需要使用 otherSSLIOHandler
)。
坦率地说,您的 Delphi 代码甚至没有接近复制 Javascript 代码所做的事情。试试这样的东西:
unit uMainForm;
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, IdBaseComponent, IdSSLOpenSSL, IdSSLOpenSSLHeaders,
System.NetEncoding, IdComponent, IdTCPConnection, IdTCPClient, IdHTTP;
type
TMainForm = class(TForm)
GetBtn: TButton;
ResponseMemo: TMemo;
procedure GetBtnClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
{ Private declarations }
sl: TStringList;
public
{ Public declarations }
end;
var
MainForm: TMainForm;
implementation
{$R *.dfm}
procedure TMainForm.FormCreate(Sender: TObject);
begin
sl := TStringList.Create;
sl.Add('locations=[{"address":"Test1","lat":"52.05429","lng":"4.248618"},{"address":"Test2","lat":"52.076892","lng":"4.26975"},{"address":"Test3","lat":"51.669946","lng":"5.61852"},{"address":"Sint-Oedenrode, The Netherlands","lat":"51.589548","lng":"5.432482"}]');
end;
procedure TMainForm.FormDestroy(Sender: TObject);
begin
sl.Free;
end;
procedure TMainForm.GetBtnClick(Sender: TObject);
var
LHTTP: TIdHTTP;
LHandler: TIdSSLIOHandlerSocketOpenSSL;
responseFromServer, sWhichFail, sVer :string;
begin
ResponseMemo.Clear;
LHTTP := TIdHTTP.Create(nil);
try
LHandler := TIdSSLIOHandlerSocketOpenSSL.Create(LHTTP);
LHandler.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
LHandler.SSLOptions.Mode := sslmClient;
LHandler.SSLOptions.VerifyMode := [];
LHandler.SSLOptions.VerifyDepth := 0;
LHTTP.IOHandler := LHandler;
LHTTP.AllowCookies := True;
LHTTP.Request.Accept := 'text/json, */*';
LHTTP.Request.BasicAuthentication := True;
LHTTP.Request.Username := 'username';
LHTTP.Request.Password := 'password';
LHTTP.HTTPOptions := [hoForceEncodeParams];
try
responseFromServer := IdHTTP1.Post('https://api.routexl.com/tour', sl);
ResponseMemo.Lines.Text := responseFromServer;
except
on E: EIdOSSLCouldNotLoadSSLLibrary do
begin
sVer := OpenSSLVersion();
sWhichFail := WhichFailedToLoad();
ResponseMemo.Lines.Add(sVer);
ResponseMemo.Lines.Add(sWhichFail);
ShowMessage('Could not load OpenSSL');
end;
on E: EIdHTTPProtocolException do
begin
ResponseMemo.Lines.Add(Format('Response Code: %d', [LHTTP.ResponseCode]));
ResponseMemo.Lines.Add(Format('Response Text: %s', [LHTTP.ResponseText]));
ResponseMemo.Lines.Add(E.ErrorMessage);
ShowMessage('HTTP Failure');
end;
on E: Exception do
begin
ResponseMemo.Lines.Add(E.ClassName);
ResponseMemo.Lines.Add(E.Message);
ShowMessage('Unknown Error');
end;
end;
finally
LHTTP.Free;
end;
end;
end.
UPDATE:也就是说,API documentation 显示了以下卷曲示例:
,该示例确实转换为上述 Delphi 代码Get an unoptimized route using cURL:
curl \ --url https://api.routexl.com/tour/ \ --user <username>:<password> \ --data 'locations=[{"address":"1","lat":"52.05429","lng":"4.248618"},{"address":"2","lat":"52.076892","lng":"4.26975"},{"address":"3","lat":"51.669946","lng":"5.61852"},{"address":"4","lat":"51.589548","lng":"5.432482"}]&skipOptimisation=true'
-d, --data
(HTTP MQTT) Sends the specified data in a POST request to the HTTP server, in the same way that a browser does when a user has filled in an HTML form and presses the submit button. This will cause curl to pass the data to the server using the content-type application/x-www-form-urlencoded. Compare to -F, --form.
除一个细节外 - 当发送 application/x-www-form-urlencoded
请求并启用 hoForceEncodeParams
选项时,TIdHTTP.Post()
将对 []
、{}
、"
、:
和 ,
字符在 %HH
格式的 JSON 中。但是 GitHub 上的 API 的 Javascript and PHP examples 没有进行这种编码,他们按原样发送 JSON。因此,如果 API 服务器不喜欢该编码,则只需禁用 hoForceEncodeParams
选项:
LHTTP.HTTPOptions := [];