如何在Delphi中实现SSL以连接到一个REDCap API服务器?

huangapple go评论72阅读模式
英文:

How do I implement SSL in Delphi to connect to a REDCap API server?

问题

我试图使用XE7连接到内部的REDCap服务器。REDCap在https://education.arcus.chop.edu/redcap-api/上有API的详细描述,并且在https://bbmc.ouhsc.edu/redcap/api 上有一个测试服务器以及一个测试令牌密钥。在R中有一些辅助信息,可以在https://mran.microsoft.com/snapshot/2015-08-18/web/packages/REDCapR/vignettes/TroubleshootingApiCalls.html中找到。

我可以使用Curl和PostMan连接到测试站点。我的问题是如何在Delphi中使用SSL实现这一点。

以下是来自PostMan的Curl脚本:

curl --location 'https://bbmc.ouhsc.edu/redcap/api/' \
--data-urlencode 'token=9A81268476645C4E5F03428B8AC3AA7B' \
--data-urlencode 'content=record' \
--data-urlencode 'action=export' \
--data-urlencode 'format=csv' \
--data-urlencode 'rawOrLabel=label';

经过多次搜索,这是我的Delphi代码。我漏掉了什么吗?IdLogFile1是窗体上的一个组件。

function TForm1.IdSSLIOHandlerSocketOpenSSL1VerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
   showmessage('at IOhandler');                      
   Result := true;                             // always returns true
end;

procedure TForm1.idHTTP2BtnClick(Sender: TObject);
var
   post      : string;
   Params    : TStringList;
   idHTTP    : TIdHTTP;
   SSL1      : TIdSSLIOHandlerSocketOpenSSL;
   status    : integer;
   response  : TstringStream;
begin
   params   := TStringList.Create;
   idHTTP   := TIdHTTP.Create(nil);
   SSL1     := TIdSSLIOHandlerSocketOpenSSL.Create(idHTTP);
   response := TstringStream.create;

   SSL1.SSLOptions.Mode        := sslmClient;
   SSL1.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
   SSL1.SSLOptions.VerifyDepth := 0;
   SSL1.OnVerifyPeer           := IdSSLIOHandlerSocketOpenSSL1VerifyPeer;
   SSL1.SSLOptions.VerifyMode  := [];
   idHTTP.IOHandler            := SSL1;

   memo1.Lines.clear;

   idHTTP.ReadTimeout                 := 3000;
   idHTTP.ConnectTimeout              := 3000;
   idHttp.Request.BasicAuthentication := false;

   try
      idHTTP.HandleRedirects := true;
      idHTTP.Intercept       := IdLogFile1;
      IdLogFile1.Active      := true;

      IdHttp.Request.CustomHeaders.Clear;

      IdHttp.Request.CustomHeaders.Values['token']          := '9A81268476645C4E5F03428B8AC3AA7B';
      IdHttp.Request.CustomHeaders.Values['content']        := 'record';
      IdHttp.Request.CustomHeaders.Values['action']         := 'export';
      IdHttp.Request.CustomHeaders.Values['format']         := 'csv';
      IdHttp.Request.CustomHeaders.Values['rawOrLabel']     := 'label';
      IdHttp.Request.CustomHeaders.Values['verify_ssl']     := 'false';
      IdHttp.Request.CustomHeaders.Values['ssl_verify']     := 'false';
      IdHttp.Request.CustomHeaders.Values['ssl_verifypeer'] := 'false';

      idHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
      IdHTTP.Request.Charset     := 'utf-8';
      idHTTP.HTTPOptions         := [hoKeepOrigProtocol, hoForceEncodeParams];

      idHTTP.Post('https://bbmc.ouhsc.edu/redcap/api/', params, response);
   finally
      memo1.Lines.add(' ');
      memo1.lines.add(idHTTP.ResponseText);
      memo1.Lines.add(' ');
      status := idHTTP.ResponseCode;
      memo1.Lines.Add('code: ' + inttostr(status));

      idhttp.Disconnect;
   end;
   Params.Free;
   SSL1.Free;
   idHTTP.Free;
   response.Free;
end;
英文:

I am trying to use XE7 to connect to an in-house REDCap server. REDCap has a detailed description of the API at https://education.arcus.chop.edu/redcap-api/ and a test server at https://bbmc.ouhsc.edu/redcap/api with a test token key. There is assistance at https://mran.microsoft.com/snapshot/2015-08-18/web/packages/REDCapR/vignettes/TroubleshootingApiCalls.html in R.

I can connect to the test site with Curl and PostMan. My problem is how to implement this in Delphi with SSL.

The Curl script from PostMan:

<!-- language: none -->

curl --location &#39;https://bbmc.ouhsc.edu/redcap/api/&#39; \
--data-urlencode &#39;token=9A81268476645C4E5F03428B8AC3AA7B&#39; \
--data-urlencode &#39;content=record&#39; \
--data-urlencode &#39;action=export&#39; \
--data-urlencode &#39;format=csv&#39; \
--data-urlencode &#39;rawOrLabel=label&#39;

After much searching, this is my Delphi code. What have I missed? IdLogFile1 is a component on the form.

<!-- language: delphi -->

function TForm1.IdSSLIOHandlerSocketOpenSSL1VerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean;
begin
   showmessage(&#39;at  IOhandler&#39;);                      
     Result := true;                             // always returns true
end;


procedure TForm1.idHTTP2BtnClick(Sender: TObject);
var post      : string;
    Params    : TStringList;
    idHTTP    : TIdHTTP;
    SSL1      : TIdSSLIOHandlerSocketOpenSSL;
    status    : integer;
    response : TstringStream;
begin
   params   := TStringList.Create;
   idHTTP   := TIdHTTP.Create(nil);
   SSL1     := TIdSSLIOHandlerSocketOpenSSL.Create(idHTTP);
   response  := TstringStream.create;


   SSL1.SSLOptions.Mode        := sslmClient ;
   SSL1.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2 ];// [  sslvSSLv3,  sslvSSLv23,sslvSSLv2, sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2];
   SSL1.SSLOptions.VerifyDepth := 0;
   SSL1.OnVerifyPeer           := IdSSLIOHandlerSocketOpenSSL1VerifyPeer;
   SSL1.SSLOptions.VerifyMode  := [ ];
   idHTTP.IOHandler            := SSL1;

   memo1.Lines.clear;

   idHTTP.ReadTimeout                 := 3000;
   idHTTP.ConnectTimeout              := 3000;
   idHttp.Request.BasicAuthentication := false;

   try

     idHTTP.HandleRedirects := true;
     idHTTP.Intercept       := IdLogFile1;
     IdLogFile1.Active      := true;

     IdHttp.Request.CustomHeaders.Clear;

 
     IdHttp.Request.CustomHeaders.Values[&#39;token&#39;]          := &#39;9A81268476645C4E5F03428B8AC3AA7B&#39;;
     IdHttp.Request.CustomHeaders.Values[&#39;content&#39;]        := &#39;record&#39;;
     IdHttp.Request.CustomHeaders.Values[&#39;action&#39;]         := &#39;export&#39;;
     IdHttp.Request.CustomHeaders.Values[&#39;format&#39;]         := &#39;csv&#39;;
     IdHttp.Request.CustomHeaders.Values[&#39;rawOrLabel&#39;]     := &#39;label&#39;;
     IdHttp.Request.CustomHeaders.Values[&#39;verify_ssl&#39;]     := &#39;false&#39;;
     IdHttp.Request.CustomHeaders.Values[&#39;ssl_verify&#39;]     := &#39;false&#39;; //various verify options ?
     IdHttp.Request.CustomHeaders.Values[&#39;ssl_verifypeer&#39;] := &#39;false&#39;;

 
     idHTTP.Request.ContentType := &#39;application/x-www-form-urlencoded&#39;;
     IdHTTP.Request.Charset     := &#39;utf-8&#39;;
     idHTTP.HTTPOptions         := [hoKeepOrigProtocol, hoForceEncodeParams];

     idHTTP.Post(&#39;https://bbmc.ouhsc.edu/redcap/api/&#39;, params, response );


   finally
        memo1.Lines.add(&#39; &#39;);
        memo1.lines.add(idHTTP.ResponseText);
        memo1.Lines.add(&#39; &#39;);
        status           := idHTTP.ResponseCode;
        memo1.Lines.Add(&#39;code: &#39; + inttostr(status));
 
        idhttp.Disconnect;
 
   end;
   Params.Free;
   SSL1.Free;
   idHTTP.Free;
   response.Free;
end;

答案1

得分: 1

你已经正确设置了TLS连接(假设Indy可以找到适当的OpenSSL DLLs)。

但您没有正确设置数据参数。Curl的--data-urlencode命令将数据放在HTTP请求正文中,而不是HTTP头中。因此,您需要将数据放入您正在发送的TStringList中(TIdHTTP会为您处理URL编码)。

请尝试这样做:

    procedure TForm1.idHTTP2BtnClick(Sender: TObject);
    var
      params    : TStringList;
      idHTTP    : TIdHTTP;
      idSSL     : TIdSSLIOHandlerSocketOpenSSL;
      status    : integer;
      response  : string;
    begin
      params := TStringList.Create;
      try
        idHTTP := TIdHTTP.Create(nil);
        try
          idSSL := TIdSSLIOHandlerSocketOpenSSL.Create(idHTTP);    
    
          idSSL.SSLOptions.Mode        := sslmClient ;
          idSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2 ];
          idSSL.SSLOptions.VerifyDepth := 0;
          idSSL.OnVerifyPeer           := IdSSLIOHandlerSocketOpenSSL1VerifyPeer;
          idSSL.SSLOptions.VerifyMode  := [ ];
          idHTTP.IOHandler := idSSL;
    
          Memo1.Lines.Clear;
    
          idHTTP.ReadTimeout                 := 3000;
          idHTTP.ConnectTimeout              := 3000;
          idHTTP.Request.BasicAuthentication := false;
    
          try    
            idHTTP.HandleRedirects := true;
            idHTTP.Intercept       := IdLogFile1;
            IdLogFile1.Active      := true;

            params.Add('token=9A81268476645C4E5F03428B8AC3AA7B');
            params.Add('content=record');
            params.Add('action=export');
            params.Add('format=csv');
            params.Add('rawOrLabel=label');

            idHTTP.Request.ContentType := 'application/x-www-form-urlencoded';
            idHTTP.Request.Charset     := 'utf-8';
            idHTTP.HTTPOptions         := [hoKeepOrigProtocol, hoForceEncodeParams];
    
            response := idHTTP.Post('https://bbmc.ouhsc.edu/redcap/api/', params);    
          finally
            Memo1.Lines.Add(' ');
            Memo1.Lines.Add(idHTTP.ResponseText);
            Memo1.Lines.Add(' ');
            status := idHTTP.ResponseCode;
            Memo1.Lines.Add('code: ' + IntToStr(status));
          end;
        finally
          idHTTP.Free;
        end;
      finally
        params.Free;
      end;
    end;
英文:

You are setting up the TLS connection correctly (provided the appropriate OpenSSL DLLs are available where Indy can find them).

What you are not setting up correctly is your data parameters. Curl's --data-urlencode command puts the data in the HTTP request body, not in the HTTP headers. So you need to put the data in the TStringList that you are posting (TIdHTTP will handle the url-encoding for you).

Try this instead:

procedure TForm1.idHTTP2BtnClick(Sender: TObject);
var
  params    : TStringList;
  idHTTP    : TIdHTTP;
  idSSL     : TIdSSLIOHandlerSocketOpenSSL;
  status    : integer;
  response  : string;
begin
  params := TStringList.Create;
  try
    idHTTP := TIdHTTP.Create(nil);
    try
      idSSL := TIdSSLIOHandlerSocketOpenSSL.Create(idHTTP);    

      idSSL.SSLOptions.Mode        := sslmClient ;
      idSSL.SSLOptions.SSLVersions := [sslvTLSv1, sslvTLSv1_1, sslvTLSv1_2 ];
      idSSL.SSLOptions.VerifyDepth := 0;
      idSSL.OnVerifyPeer           := IdSSLIOHandlerSocketOpenSSL1VerifyPeer;
      idSSL.SSLOptions.VerifyMode  := [ ];
      idHTTP.IOHandler := idSSL;

      Memo1.Lines.Clear;

      idHTTP.ReadTimeout                 := 3000;
      idHTTP.ConnectTimeout              := 3000;
      idHTTP.Request.BasicAuthentication := false;

      try    
        idHTTP.HandleRedirects := true;
        idHTTP.Intercept       := IdLogFile1;
        IdLogFile1.Active      := true;

        params.Add(&#39;token=9A81268476645C4E5F03428B8AC3AA7B&#39;);
        params.Add(&#39;content=record&#39;);
        params.Add(&#39;action=export&#39;);
        params.Add(&#39;format=csv&#39;);
        params.Add(&#39;rawOrLabel=label&#39;);

        idHTTP.Request.ContentType := &#39;application/x-www-form-urlencoded&#39;;
        idHTTP.Request.Charset     := &#39;utf-8&#39;;
        idHTTP.HTTPOptions         := [hoKeepOrigProtocol, hoForceEncodeParams];

        response := idHTTP.Post(&#39;https://bbmc.ouhsc.edu/redcap/api/&#39;, params);    
      finally
        Memo1.Lines.Add(&#39; &#39;);
        Memo1.Lines.Add(idHTTP.ResponseText);
        Memo1.Lines.Add(&#39; &#39;);
        status := idHTTP.ResponseCode;
        Memo1.Lines.Add(&#39;code: &#39; + IntToStr(status));
      end;
    finally
      idHTTP.Free;
    end;
  finally
    params.Free;
  end;
end;

答案2

得分: 0

Two simpler alternatives with System.Net.HttpClient and mORMot. Both work as expected with same results. Tested with Delphi 11.

program Project1;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Classes,
  System.Net.HttpClient,
  Syncommons,
  Syncrtsock;

var
  http: TWinHttp;
  ResHeader, ResData: SockString;
  Status: cardinal;

  Client: THTTPClient;
  Response: IHTTPResponse;
  params: TStringList;
begin
  /// mORMot alternative
  http := TWinHttp.Create('bbmc.ouhsc.edu', '443', true);
  try

    Status := http.Request('/redcap/api/',
      'POST',
      0,
      'Content-Type: application/x-www-form-urlencoded',
      'token=9A81268476645C4E5F03428B8AC3AA7B&content=record&action=export&format=csv&rawOrLabel=label',
      '',
      ResHeader,
      ResData);
  finally
    http.Free;
  end;
  writeln(formatUTF8('mORMot -> Status= % Response=%', [Status, resdata]));

  /// HttpClient alternative
  Client := THTTPClient.Create();
  try
    params := TStringList.Create;
    try
      params.Add('token=9A81268476645C4E5F03428B8AC3AA7B');
      params.Add('content=record');
      params.Add('action=export');
      params.Add('format=csv');
      params.Add('rawOrLabel=label');
      Client.CustHeaders.Add('Content-Type', 'application/x-www-form-urlencoded');
      Response := Client.Post('https://bbmc.ouhsc.edu/redcap/api/', params);
      writeln(formatUTF8('HttpClient -> Status= % Response=%', [Response.StatusCode, Response.ContentAsString]));

      Assert(Resdata = Response.ContentAsString); // checked!
    finally
      params.Free;
    end;
  finally
    Client.Free;
  end;
  readln;
end.
英文:

Two simpler alternatives with System.Net.HttpClient and mORMot . Both work as expected with same results. Tested with Delphi 11.

program Project1;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Classes,
  System.Net.HttpClient,
  Syncommons,
  Syncrtsock;

var
  http : TWinHttp;
  ResHeader,ResData : SockString;
  Status : cardinal;

  Client: THTTPClient;
  Response: IHTTPResponse;
  params : TStringList;
begin
    /// mORMot alternative
    http := TWinHttp.Create(&#39;bbmc.ouhsc.edu&#39;,&#39;443&#39;,true);
    try

      Status := http.Request(&#39;/redcap/api/&#39;,
                             &#39;POST&#39;
                             ,0,
                             &#39;Content-Type: application/x-www-form-urlencoded&#39;,
                             &#39;token=9A81268476645C4E5F03428B8AC3AA7B&amp;content=record&amp;action=export&amp;format=csv&amp;rawOrLabel=label&#39;,
                             &#39;&#39;,
                             ResHeader,
                             ResData);
    finally
      http.Free;
    end;
    writeln(formatUTF8(&#39;mORMot -&gt; Status= % Response=%&#39;,[Status , resdata ]));

    /// HttpClient alternative
    Client := THTTPClient.Create();
    try
      params := TStringList.Create;
      try
        params.Add(&#39;token=9A81268476645C4E5F03428B8AC3AA7B&#39;);
        params.Add(&#39;content=record&#39;);
        params.Add(&#39;action=export&#39;);
        params.Add(&#39;format=csv&#39;);
        params.Add(&#39;rawOrLabel=label&#39;);
        Client.CustHeaders.Add(&#39;Content-Type&#39; , &#39;application/x-www-form-urlencoded&#39;);
        Response := Client.Post(&#39;https://bbmc.ouhsc.edu/redcap/api/&#39;, params);
        writeln(formatUTF8(&#39;HttpClient -&gt; Status= % Response=%&#39;,[Response.StatusCode, Response.ContentAsString]));

        Assert(Resdata = Response.ContentAsString); // checked!
      finally
        params.Free;
      end;
    finally
      Client.Free;
    end;
    readln;
end.

huangapple
  • 本文由 发表于 2023年6月1日 12:47:49
  • 转载请务必保留本文链接:https://go.coder-hub.com/76378736.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定