Twilio or Plivo SMS using Delphi

2019-08-05 09:20发布

问题:

I am trying to figure out how to use Delphi 10 Seattle and Indy to send a POST request to either Plivo or Twilio for sending SMS messages. When I use this code for Twilio efforts, I get an Unauthorized message in return (note that I have redacted my user name and Auth code):

procedure TSendTextForm.TwilioSendSms(FromNumber, ToNumber, Sms: string; Var Response: TStrings);

var
  apiurl, apiversion, accountsid, authtoken,
  url: string;
  aParams, aResponse: TStringStream;
  mHTTP : TidHttp;

begin

  mHTTP := TIdHTTP.Create(nil);

  apiurl := 'api.twilio.com';
  apiversion := '2010-04-01';
  accountsid := 'AC2f7cda1e6a4e74376***************:2b521b60208af4c*****************';
  url := Format('https://%s/%s/Accounts/%s/SMS/Messages/', [apiurl, apiversion, accountsid]);
  aParams := TStringStream.Create ;
try
  aParams.WriteString('&From=' + FromNumber);
  aParams.WriteString('&To=' + ToNumber);
  aParams.WriteString('&Body=' + Sms);
  aResponse := TStringStream.Create;
try
  mHTTP.Post(url, aParams, aResponse);
finally
  Response.Text  := aResponse.DataString;
end;
finally
  aParams.Free;
end;
end;

I have similar code for Plivo. Neither company has any Delphi support. Can anyone tell me what I'm missing above here? Thanks so much.

Mic

回答1:

Twilio evangelist here.

In addition to the Basic Auth suggestion made by @mjn above, there are two other issues in your sample I believe will cause you problems:

First, in the code example above, your URL will be wrong because the accountsid variable is concatenating together both your sid and auth token.

accountsid := 'AC2f7cda1e6a4e74376***************:2b521b60208af4c*****************';

While you do want to do this in order to use Basic Authentication, you don't want the auth token as part of the URL. When you create the url property you just want to put the SID into the URL as the parameter like this:

/Accounts/ACXXXXXXX/

Second, I would also suggest not using the /SMS resources as its deprecated. Instead use /Messages which is newer and has more features:

/Accounts/ACXXXXXXX/Messages



回答2:

The REST API docs on https://www.twilio.com/docs/api/rest say that basic auth is used:

HTTP requests to the REST API are protected with HTTP Basic authentication.

TIdHTTP has built-in support for Basic authentication. Simply set the TIdHTTP.Request.BasicAuthentication property to true, and set IdHTTP.Request.Username and TIdHTTP.Request.Password properties as needed.

Other hints:

  • TIdHTTP.Create(nil) can be shortened to TIdHTTP.Create
  • the var modifier for Response can be changed to const

Your code also leaks memory because the Indy component is not freed.



回答3:

In Delphi 10 Seattle, there is some REST components with TRestClient

And TMS made components for Twillio in their TMS Cloud Pack http://www.tmssoftware.com/site/cloudpack.asp



回答4:

It's funny, but I was just going to post the same question about plivo. I have been also trying to get both twilio and plivo to work. I did manage to finally get twilio to work. Plivo on the other hand would not work with the same code, even though they both are practically the same.

I used REST functions in delphi. The following code is used for both twilio and plivo.

procedure TForm1.FormCreate(Sender: TObject);
begin
        // TypeCo
        // = T = Twilio
        // = P = Plivo

        TypeCo := 'T';


        if TypeCo='T' then // Twillio
        begin
            AccountSid := 'ACd27xxxxxxxxxxxxxxxxxxxxxxb106e38'; // x's were replaced to hide ID 
            AuthToken := '24fxxxxxxxxxxxxxxxxxxxxxxxxf08ed'; // x's were replaced to hide Token
            BaseURL := 'https://api.twilio.com';
            Resource := '/2010-04-01/Accounts/'+accountSid+'/Messages';
        end
        else if TypeCO='P' then // Plivo
        begin
            AccountSid := 'MANTxxxxxxxxxxxxxXYM';
            AuthToken := 'ZDg0OxxxxxxxxxxxxxxxxxxxxxxxxxxxxjM5Njhh';
            BaseURL := 'https://api.plivo.com';
            Resource := '/v1/Account/'+accountSid+'/Message/';
        end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
         RESTClient := TRESTClient.Create(BaseURL);
         try
             RESTRequest := TRESTRequest.Create(RESTClient);
             try
                 RESTResponse := TRESTResponse.Create(RESTClient);
                 try
                     HTTPBasicAuthenticator := THTTPBasicAuthenticator.Create('AC', 'c1234');

                     try
                         RESTRequest.ResetToDefaults;

                         RESTClient.BaseURL := BaseURL;
                         RESTRequest.Resource := Resource;
                         HTTPBasicAuthenticator.UserName := AccountSid;
                         HTTPBasicAuthenticator.Password := AuthToken;
                         RESTClient.Authenticator := HTTPBasicAuthenticator;
                         RESTRequest.Client := RESTClient;
                         RESTRequest.Response := RESTResponse;

                         // Tried this to fix plivo error with no luck!
                         // RESTClient.Params.AddHeader('Content-Type', 'application/json');

                         // "From" number is the send # setup in the twilio or plivo account.  The "To" number is a verified number in your twilio or plivo account

                         if TypeCo='T' then // Twilio
                             SendSMS( '+1602xxxxx55','+1602xxxxxx7', 'This is a test text from Twilio') // x's were replaced to hide telephone numbers 
                         else if TypeCo='P' then // Plivo
                             SendSMS( '1602xxxxx66','1602xxxxxx7', 'This is a test text from Plivo');  // x's were replaced to hide telephone numbers

                     finally
                         HTTPBasicAuthenticator.Free;
                     end;
                 finally
                     RESTResponse.Free;
                 end;
             finally
                   RESTRequest.Free;
             end;
         finally
              RESTClient.Free;
         end;

end;


function TForm1.SendSMS(aFrom, aTo, aText: string): boolean;
begin
    result := True;
    RESTRequest.ResetToDefaults;

    RESTClient.BaseURL := BaseURL;
    RESTRequest.Resource := Resource;
    if TypeCo='T' then // Twilio
   begin
        RESTRequest.Params.AddUrlSegment('AccountSid', accountSid);
        RESTRequest.Params.AddItem('From', aFrom);
        RESTRequest.Params.AddItem('To', aTo);
        RESTRequest.Params.AddItem('Body', aText);
   end
   else if TypeCo='P' then // Plivo
   begin
        RESTRequest.Params.AddUrlSegment('AccountSid', accountSid);
        RESTRequest.Params.AddItem('src', aFrom);
        RESTRequest.Params.AddItem('dst', aTo);
        RESTRequest.Params.AddItem('text', aText);
   end;


   RESTRequest.Method := rmPOST;
   RESTRequest.Execute;

   // Show Success or Error Message
   ErrorMsg.Clear;
   ErrorMsg.Lines.Text := RESTResponse.Content;

end;

As mentioned, the code above works fine for Twilio. However, for Plivo I'm getting the following error:

{
  "api_id": "b124d512-b8b6-11e5-9861-22000ac69cc8",
  "error": "use 'application/json' Content-Type and raw POST with json data"
}

I have been trying to determine how to fix this problem. I contacted Plivo support and I received the following response:

 The error "use 'application/json' Content-Type and raw POST with json data" is generated when the Header "Content-Type" is not set the value "application/json"
 Please add the Header Content-Type under Items in the Request Tab and set the Description as application/json.

I tried adding the code in the button procedure:

RESTClient.Params.AddHeader('Content-Type', 'application/json');

but it still gives the same error. I think this code is really close to working for Plivo. I'm a newbie at REST functions so I'm not sure what else to try. I have tried assigning "application/json" to just about everything that will accept it and still get the same error. Hopefully, someone else will have an idea of what will make Plivo work.



回答5:

Finally, I was able to get both Twilio and Plivo to send SMS messages. With the help of the Delphi REST Debugger and comments made in this thread, I was finally able to figure it out. Thank you! In the SendSMS function (for Plivo), instead of adding each parameter, I had to add a "Content-Type" header of "application/json" and add the parameters as RAW in the body using RESTRequest.AddBody(). One more note to make, I was originally testing this in Delphi XE5 and it would continue to give me the same error mentioned in an earlier post. When I tried it in Delphi XE7, it finally worked!!! This should work in Delphi 10 Seattle too, but haven't tested it!

Here is the working demo. On the form you need to have the following components:

Button1: TButton;
RESTClient: TRESTClient;
RESTRequest: TRESTRequest;
RESTResponse: TRESTResponse;
HTTPBasicAuthenticator: THTTPBasicAuthenticator;
ResponseMsg: TMemo;

In the OnCreate, change TypeCo from "T" or "P" depending on which company you want to test. Then run it!

Here is the code. Note, I masked out the AccountSid, AuthToken, and phone number fields for privacy.

procedure TForm1.FormCreate(Sender: TObject);
begin
      // TypeCo
      // = T = Twilio
      // = P = Plivo

      TypeCo := 'P';

      if TypeCo='T' then // Twilio
      begin
          AccountSid := 'ACd2*************************06e38'; 
          AuthToken := '24f63************************8ed'; 
          BaseURL := 'https://api.twilio.com';
          Resource := '/2010-04-01/Accounts/'+accountSid+'/Messages';
      end
      else if TypeCO='P' then // Plivo
      begin
          AccountSid := 'MAN*************IXYM';
          AuthToken := 'ZDg0*******************************5Njhh';
          BaseURL := 'https://api.plivo.com';
          Resource := '/v1/Account/'+accountSid+'/Message/';
      end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var i:integer;
begin
     RESTClient.ResetToDefaults;
     RESTRequest.ResetToDefaults;
     ResponseMsg.Clear;

     RESTClient.BaseURL := BaseURL;
     RESTRequest.Resource := Resource;
     HTTPBasicAuthenticator.UserName := AccountSid;
     HTTPBasicAuthenticator.Password := AuthToken;
     RESTClient.Authenticator := HTTPBasicAuthenticator;
     RESTRequest.Client := RESTClient;
     RESTRequest.Response := RESTResponse;

     // Here is where you can loop through your messages for different recipients.
     // I use the same "To" phone number in this test
     for i := 1 to 3 do
     begin
           if TypeCo='T' then // Twilio
               SendSMS( '+1602******0','+1602******7', Format('This is test #%s using Twilio. Sent at %s',[inttostr(i),TimeToStr(Time)]))
           else if TypeCo='P' then // Plivo
               SendSMS( '1662******2','1602******7', Format('This is test #%s using Plivo. Sent at %s',[inttostr(i),TimeToStr(Time)]));

           // Show Success or Error Message
          ResponseMsg.Lines.Add(RESTResponse.Content);
          ResponseMsg.Lines.Add('');
          ResponseMsg.Refresh;

     end;
end;


function TForm1.SendSMS(aFrom, aTo, aText: string):Boolean;
begin
     result := False;
     RESTRequest.Params.Clear;
     RESTRequest.ClearBody;


     RESTClient.BaseURL := BaseURL;
     RESTRequest.Resource := Resource;
     if TypeCo='T' then // Twilio
     begin
          RESTRequest.Params.AddUrlSegment('AccountSid', accountSid);
          RESTRequest.Params.AddItem('From', aFrom);
          RESTRequest.Params.AddItem('To', aTo);
          RESTRequest.Params.AddItem('Body', aText);
     end
     else if TypeCo='P' then // Plivo
     begin
          // NOTE: The following Header line needs to be commented out for Delphi Seattle 10 Update 1 (or probably newer) to work. The first original Delphi 10 Seattle version would not work at all for Plivo. In this case the following error would occur: "REST request failed: Error adding header: (87) The parameter is incorrect".  This was due to the HTTPBasicAuthenticator username and password character lengths adding up to greater than 56.
          RESTRequest.Params.AddItem('Content-Type', 'application/json', pkHTTPHEADER, [], ctAPPLICATION_JSON);
       // RESTRequest.Params.AddItem('body', '', pkRequestBody, [], ctAPPLICATION_JSON);  // Thought maybe I needed this code, but works without it.
          RESTRequest.AddBody(Format('{"src":"%s","dst":"%s","text":"%s"}',[aFrom,aTo,aText]),ctAPPLICATION_JSON);
     end;

     RESTRequest.Method := rmPOST;
     RESTRequest.Execute;
     result := True;
end;