1

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:

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'

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

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 ];// [  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['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'; //various verify options ?
     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;
2
  • Do you have the OpenSSL DLL's (libeay32.dll and ssleay32.dll) with your EXE? docwiki.embarcadero.com/RADStudio/Sydney/en/… Commented Jun 1, 2023 at 12:37
  • Used latest version u of the dll's Commented Jun 1, 2023 at 23:39

2 Answers 2

1

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('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;
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks Remy works a treat. I have spent a long time puzzling over this.
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.

3 Comments

Thank you Xalo . I am using XE7 as we have not gone to XE11 yet.
@MrGee that kind of information should be given up front.
@MrGee you are welcome. You can use mORMot option provided from Delphi 7 to Delphi 11 if you need an alternative.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.