I have been using Indy's TIdTCPClient component successfully for quite a while now to send push notifications to my mobile app users. Lately, due to new push notification requirements of Apple, this feature has stopped being functional for iOS users.
For the past three days, I've been trying to reconstruct this function by using the TIdHTTP component instead, following the new HTTP/2 format advised by Apple to send push notifications. Unfortunately, I've yet to come up with something operational.
Here's my failing code:
procedure TForm5.BtnPushClick(Sender: TObject);
var
vHTTP: TIdHTTP;
SslIOHandlerHTTP : TIdSSLIOHandlerSocketOpenSSL;
RequestBody: TStream;
ResponseBody, vCertFileName: string;
vJsonToSend : TStringStream;
begin
try
vCertFileName := 'apns-dist.pem';
vIdHTTP := TIdHTTP.Create(nil);
SslIOHandlerHTTP := TIdSSLIOHandlerSocketOpenSSL.Create(vHTTP);
SslIOHandlerHTTP.OnVerifyPeer := SslIOHandlerHTTP1VerifyPeer;
SslIOHandlerHTTP.OnGetPassword := SslIOHandlerHTTP1GetPassword;
SslIOHandlerHTTP.SSLOptions.CertFile := vCertFileName;
SslIOHandlerHTTP.SSLOptions.KeyFile := vCertFileName;
SslIOHandlerHTTP.SSLOptions.Method := sslvTLSv1_2;
SslIOHandlerHTTP.SSLOptions.Mode := sslmBoth;
SslIOHandlerHTTP.SSLOptions.SSLVersions := [sslvTLSv1_2];
SslIOHandlerHTTP.SSLOptions.VerifyMode := [sslvrfPeer];
SslIOHandlerHTTP.SSLOptions.VerifyDepth := 2;
//--
vHTTP.Request.CustomHeaders.AddValue(':method', 'POST');
vHTTP.Request.CustomHeaders.AddValue(':scheme', 'https');
vHTTP.Request.CustomHeaders.AddValue(':path', '/3/device/111890052839d3ed4f596c94712329e20812464afe30ebd28487c4e65986c6f0');
vHTTP.Request.CustomHeaders.AddValue('host', 'api.push.apple.com');
vHTTP.Request.CustomHeaders.AddValue('apns-id', 'eabeae54-14a8-11e5-b60b-1697f925ec7b');
vHTTP.Request.CustomHeaders.AddValue('apns-push-type', 'alert');
vHTTP.Request.CustomHeaders.AddValue('apns-expiration', '0');
vHTTP.Request.CustomHeaders.AddValue('apns-priority', '10');
vHTTP.Request.CustomHeaders.AddValue('apns-topic', 'com.TS.JACK');
//--
vHTTP.IOHandler := SslIOHandlerHTTP;
vHTTP.Request.Accept := 'application/json';
vHTTP.Request.ContentType := 'application/json';
vHTTP.HTTPOptions := [hoForceEncodeParams];
vHTTP.ProtocolVersion := pv1_1;
SslIOHandlerHTTP.PassThrough := true;
vJsonToSend := TStringStream.Create('{ "aps" : { "alert" : "Hello" } }', TEncoding.UTF8);
ResponseBody := vHTTP.Post('https://api.push.apple.com', vJsonToSend);
except
on E: Exception do
begin
Memo1.Lines.Add(IntToStr(vHTTP.ResponseCode)+':' + E.Message);
end;
end;
Related
A while ago I wrote a method in Delphi 2010 to get the OAuth2 token using the Indy components (TidHttp). See code below.
I am now doing something new in Delphi 10.4 and would like to use the REST components such as RESTClient, RESTRequest, TOAuth2Authenticator, etc.
Our grant type is Client Credentials but in none of the examples on the net could I find on how to use TOAuth2Authenticator with Client Credentials. Is it even possible?
We have a client id, client secret and token URL. We do not have authorization or redirect endpoints. In Insomnia, the setup will look like this:
Does somebody know how to get the token using TOAuth2Authenticator with grant type = client_credentials?
Here is the Delphi 2010 code:
procedure TfrmToken.btnGetTokenClick(Sender: TObject);
var
IdHTTP: TidHttp;
lsHttpError: string;
loRequest: TStringStream;
loRespJson: TMemoryStream;
liSuper: iSuperObject;
ldtExpiry: TDateTime;
begin
IdHTTP := TIdHTTP.Create();
loRespJson := TMemoryStream.Create();
try
IdHTTP.HandleRedirects := False;
loRequest := TStringStream.Create('grant_type=client_credentials&client_id=' +
edtKey.Text + '&client_secret='+edtSecret.Text);
try
IdHttp.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdHttp);
IdHttp.Request.ContentType := 'application/x-www-form-urlencoded';
try
IdHTTP.Post(edtURL.Text, loRequest, loRespJson);
except
on E: EIdHTTPProtocolException do
begin
lsHttpError := E.ErrorMessage;
end;
on E: Exception do
begin
lsHttpError := E.Message;
end;
end;
if idHTTP.ResponseCode = 200 then
begin
liSuper := SO(StreamToString(loRespJSon));
edtToken.Text := liSuper.S['access_token'];
ldtExpiry := IncSecond(Now, liSuper.i['expires_in']);
edtExpiry.Text := 'Expires in ' + liSuper.S['expires_in'] +
' seconds. Time: ' +
FormatDateTime('yyyy/dd/mm hh:nn:ss', ldtExpiry);
end
else
begin
liSuper := SO(lsHttpError);
edtToken.Text := IdHTTP.ResponseText;
edtExpiry.Text := '';
end;
finally
FreeAndNil(loRequest);
end;
finally
FreeAndNil(IdHTTP);
FreeAndNil(loRespJson);
end;
end;
I am using the trial version of DevArt's SecureBridge product. I am trying to process POST, but somehow I could not print the request data.
XML:
<test>
<a>test1</a>
<b>test2</b>
</test>
Delphi:
ScHttpWebRequest1.Method := rmPOST;
ScHttpWebRequest1.ContentType := 'text/xml';
ScHttpWebRequest1.RequestUri := 'https://test.com/api';
ScHttpWebRequest1.KeepAlive := True;
ScHttpWebRequest1.ContentLength := Length(XML);
ScHttpWebRequest1.WriteBuffer(pAnsiChar(XML), 0, Length(XML)); ///I think I'm making a mistake here.
ShowMessage(ScHttpWebRequest1.GetResponse.ReadAsString);
I have reviewed the documents, but there is a feature called RequestStream. This feature was not available in the version I downloaded. I think WriteBuffer is used instead or different. all I want to do is request a POST with XML content on the relevant site. How can I do it?
Thanks.
Here's a chunk of code that has worked for me:
var
Response: TScHttpWebResponse;
ResponseStr: string;
buf: TBytes;
begin
ScHttpWebRequest1.Method := rmPOST;
ScHttpWebRequest1.ContentType := 'text/xml';
ScHttpWebRequest1.RequestUri := 'https://test.com/api';
ScHttpWebRequest1.KeepAlive := True;
buf := TEncoding.UTF8.GetBytes(xml);
ScHttpWebRequest1.ContentLength := Length(buf);
ScHttpWebRequest1.WriteBuffer(buf);
Response:=ScHttpWebRequest1.GetResponse;
ResponseStr:=Response.ReadAsString;
end;
Based on Devart forums information you can post/put stream or strings parameters as below:
var
Request: TScHttpWebRequest;
Response: TScHttpWebResponse;
ResponseStr: string;
Stream: TFileStream;
begin
Request := TScHttpWebRequest.Create(URL);
Stream := TFileStream.Create(FileName, fmOpenRead);
try
Request.Method := rmPut;
Request.ContentType := 'application/pdf';
Request.TransferEncoding := 'binary';
Request.Headers.Add('Content-Disposition', 'form-data; name="FormFile"; filename="Document1.pdf"');
Request.ContentLength := Stream.Size;
Request.SendChunked := True;
Request.RequestStream := Stream;
Response := Request.GetResponse;
ResponseStr := Response.ReadAsString;
Response.Free;
finally
Stream.Free;
Request.Free;
end;
end;
A bit of an odd one here, when setting "UseTLS" to utUseExplicitTLS and then connecting to a mail server on its Implicit TLS port, the first attempt allows the connection and sends the email, subsequent attempts on that port correctly fail.
Just wondering if anyone has any ideas on how to avoid the false positive on the initial connect and send.
The check is to handle that non-standard ports may be getting used for a user's mail server. Pretty much all examples I've seen assume that the correct information will always be provided.
Below is the code portion that handles it (excluding error logging):
function SendTestEmail(EmailAddress: String): Boolean;
var
EmailMessage: TidMessage;
begin
IdSMTPEmail.AuthType := satDefault
IdSMTPEmail.Username := ...;
IdSMTPEmail.Password := ...;
IdSMTPEmail.Port := 465;
IdSMTPEmail.IOHandler := TIdSSLIOHandlerSocketOpenSSL.Create(IdSMTPEmail);
IdSMTPEmail.UseTLS := utUseExplicitTLS;
TIdSSLIOHandlerSocketOpenSSL(IdSMTPEmail.IOHandler).SSLOptions.Method := sslvTLSv1_2;
try
// Connect
IdSMTPEmail.Connect('smtp.gmail.com');
try
// Create
EmailMessage := TidMessage.Create(nil);
try
// Set values
EmailMessage.Body.Add('Test Email');
EmailMessage.Subject := 'Test Email';
// Set sender details
EmailMessage.From.Address := 'test#test.com';
EmailMessage.From.Name := 'SSL Test';
// Set recipient
EmailMessage.Recipients.Add.Address := EmailAddress;
try
// Send message
IdSMTPEmail.Send(EmailMessage);
except
// Exception
on E: EIdSMTPReplyError do
begin
// Result
Result := False;
end;
end;
finally
// Free email
EmailMessage.Free;
end;
finally
// Disconnect
IdSMTPEmail.Disconnect;
end;
except
// Exception
on E: Exception do
begin
IdSMTPEmail.Disconnect;
// Result
Result := False;
end;
end;
end;
Using the below code, correctly failed on all attempts of trying to email to port 465.
I don't even pretend to understand why this works but my original didn't.
function SendTestEmail(EmailAddress: String): Boolean;
var
EmailMessage: TidMessage;
IdSSLHandler: TIdSSLIOHandlerSocketOpenSSL;
begin
IdSMTPEmail.AuthType := satDefault;
IdSSLHandler := TIdSSLIOHandlerSocketOpenSSL.Create;
IdSSLHandler.SSLOptions.Method := sslvTLSv1_2;
IdSMTPEmail.IOHandler := IdSSLHandler;
IdSMTPEmail.UseTLS := utUseExplicitTLS;
IdSMTPEmail.Username := ...;
IdSMTPEmail.Password := ...;
IdSMTPEmail.Port := 465;
try
// Connect
IdSMTPEmail.Connect('smtp.gmail.com');
try
// Create
EmailMessage := TidMessage.Create(nil);
try
// Set values
EmailMessage.Body.Add('Test Email');
EmailMessage.Subject := 'Test Email';
// Set sender details
EmailMessage.From.Address := 'test#test.com';
EmailMessage.From.Name := 'SSL Test';
// Set recipient
EmailMessage.Recipients.Add.Address := EmailAddress;
try
// Send message
IdSMTPEmail.Send(EmailMessage);
except
// Exception
on E: EIdSMTPReplyError do
begin
// Result
Result := False;
end;
end;
finally
// Free email
EmailMessage.Free;
end;
finally
// Disconnect
IdSMTPEmail.Disconnect;
end;
except
// Exception
on E: Exception do
begin
IdSMTPEmail.Disconnect;
// Result
Result := False;
end;
end;
end;
All,
I am working on a new datasnap project based on the example project located in C:\Users\Public\Documents\Embarcadero\Studio\18.0\Samples\Object Pascal\DataSnap\FireDAC_DBX.
I am trying to transfer a large stream (1,606,408 bytes) from datasnap server to client. I am running into what appears to be a common issue and that is that the entire stream does not make it to the client.
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): TStringStream;
begin
Result := TStringStream.Create;
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(Result, TFDStorageFormat.sfBinary);
Result.Position := 0;
// Result.SaveToFile('output.adb');
except
raise;
end;
end;
Here is my client code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
LStringStream := TStringStream.Create(FDStoredProcBPInfo.ParamByName('ReturnValue').asBlob);
//LStringStream.Clear;
//LStringStream.LoadFromFile('Output.adb');
try
if LStringStream <> nil then
begin
LStringStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(LStringStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
LStringStream.Free;
end;
end;
You will see the stream save and load code; that is how I determined that the server was getting the entire result set into the stream, and that the client could handle the entire result set and display it properly.
So smaller streams transfer just fine, but this big one, when examined in the ide debugger, does not start with the 65,66,68,83 characters and the load fails with the error, '[FireDAC][Stan]-710. Invalid binary storage format'.
I know from extended Googling that there are work-arounds for this, but I do not understand how to apply the workarounds to my case, with the use of Tfdstoredproc and TfdSchemaAdaptor components. I'm trying to stay with this coding scheme.
How do I adapt this code to correctly receive large streams?
Update 1:
Ok, I tried strings and Base64 encoding. It didn't work.
Client Code:
procedure TdmDataSnap.GetBPInfo(CardCode : String);
var
LStringStream : TStringStream;
TempStream : TStringStream;
begin
dmDataSnap.FDStoredProcBPInfo.ParamByName('CardCode').AsString := CardCode;
FDStoredProcBPInfo.ExecProc;
try
TempStream := TStringStream.Create;
TIdDecoderMIME.DecodeStream(FDStoredProcBPInfo.ParamByName('ReturnValue').asString,TempStream);
if TempStream <> nil then
begin
TempStream.Position := 0;
try
DataModuleFDClient.FDSchemaAdapterBP.LoadFromStream(TempStream, TFDStorageFormat.sfBinary);
except
on E : Exception do
showmessage(e.Message);
end;
end;
finally
TempStream.Free;
end;
end;
Here is my server code:
//Returns Customer Info
function TServerMethods.GetBPInfo(CardCode : String): String;
var
TempStream : TMemoryStream;
OutputStr : String;
begin
Result := '';
TempStream := TMemoryStream.Create;
try
try
qBPInfo.Close;
if CardCode.Trim = '' then
qBPInfo.ParamByName('ParmCardCode').AsString := '%%'
else
qBPInfo.ParamByName('ParmCardCode').AsString := '%' + CardCode + '%';
qBPInfo.Open;
FDSchemaAdapterBPInfo.SaveToStream(TempStream, TFDStorageFormat.sfBinary);
TempStream.Position := 0;
OutputStr := IdEncoderMIMEBPInfo.EncodeStream(TempStream);
Result := OutputStr
except
raise;
end;
finally
TempStream.Free;
end;
end;
The result is the same.
I need to send a Push notification out through Parse.com's API using Delphi.
I see there is a TParseApi but the documentation is, as usual, rather sparse on the subject.
How can I do this?
Drop a TParseProvider and a TBackendPush component onto a form or datamodule. Connect them and enter your credentials in the appropriate properties of the provider. Set the backend Message property to the message to send and call Push.
There are at least three ways of doing this:
1) A direct method would be to create your own HTTP request with custom headers and JSON
Procedure TForm1.ParseDotComPushNotification(pushMessage: string);
var
parseDotComUrl: string;
JSON: TStringStream;
webRequest: TIDHttp;
response: string;
whereJson: TJSONObject;
alertJson: TJSONObject;
mainJsonObject: TJSONObject;
begin
parseDotComUrl := 'https://api.parse.com/1/push';
// Modify the JSON as required to push to whomever you want to.
// This one is set up to push to EVERYONE.
// JSON := TStringStream.Create('{ "where": {}, ' + '"data" : {"alert":"'
// + pushMessage + '"}' + '}', TEncoding.UTF8);
mainJsonObject := TJSONObject.Create;
whereJson := TJSONObject.Create;
mainJsonObject.AddPair(TJSONPair.Create('where', whereJson));
alertJson := TJSONObject.Create;
alertJson.AddPair(TJSONPair.Create('alert', pushMessage));
mainJsonObject.AddPair(TJSONPair.Create('data', alertJson));
JSON := TStringStream.Create(mainJsonObject.ToJSON);
mainJsonObject.Free; // free all the child objects.
webRequest := TIDHttp.Create(nil);
webRequest.Request.Connection := 'Keep-Alive';
webRequest.Request.CustomHeaders.Clear;
webRequest.Request.CustomHeaders.AddValue('X-Parse-Application-Id',
'YourApplicationID');
webRequest.Request.CustomHeaders.AddValue('X-Parse-REST-API-KEY',
'YourRestApiKey');
webRequest.Request.ContentType := 'application/json';
webRequest.Request.CharSet := 'utf-8';
webRequest.Request.ContentLength := JSON.Size;
try
try
response := webRequest.Post(parseDotComUrl, JSON);
except
on E: Exception do
begin
showmessage(response);
end;
end;
finally
webRequest.Free;
JSON.Free;
end;
end;
Thus bypassing the need for TParseApi
2) Based on UweRabbe's answer, you can also do it like this in code:
procedure TForm1.parseProviderCodeButtonClick(Sender: TObject);
var
myParseProvider: TParseProvider;
myBackendPush: TBackendPush;
myStrings: Tstrings;
whereJson: TJSONObject;
alertJson: TJSONObject;
mainJsonObject: TJSONObject;
begin
mainJsonObject := TJSONObject.Create;
whereJson := TJSONObject.Create;
mainJsonObject.AddPair(TJSONPair.Create('where', whereJson));
alertJson := TJSONObject.Create;
alertJson.AddPair(TJSONPair.Create('alert', pushMessage));
mainJsonObject.AddPair(TJSONPair.Create('data', alertJson));
myParseProvider := TParseProvider.Create(nil);
myParseProvider.ApiVersion := '1';
myParseProvider.ApplicationID := 'YourApplicationID';
myParseProvider.MasterKey := 'YourMasterKey';
myParseProvider.RestApiKey := 'YourRestApiKey';
myBackendPush := TBackendPush.Create(nil);
myBackendPush.Provider := myParseProvider;
// myBackendPush.Message := 'Hello world';
myStrings := TStringList.Create;
myStrings.Clear;
// I like putting the message in when I generate the JSON for the Target
// (since it seems I have to do it anyways, my not do it all in one place).
// You could however us TBackendPush.Message as I've commented out above.
// myStrings.Add('{ "where": { }, "data" : {"alert":"goodbye world"}}');
myStrings.Add(mainJsonObject.ToJSON);
myBackendPush.Target := myStrings;
myStrings.Free;
mainJsonObject.Free; // free all the child objects.
myBackendPush.Push;
myBackendPush.Free;
myParseProvider.Free;
end;
3) And to round this out into one complete answer (again based on UweRabbe's answer)
On your form/datamodule:
Place a TParseProvider
Place a TBackendPush - this should automatically set its Provider filed to the name of the TParseProvider you created in the previous step.
Set the TBackendPush's ApplicationID, MasterKey, RestApiKey, and Message properties
Set the TBackendPush's Push method from code.
e.g.,
procedure TForm1.Button1(Sender: TObject);
begin
BackendPush1.Push;
end;