I am using the Google Cloud Messaging (GCM) service for my Android app. I have implemented it according to all the rules, and it works. Well, almost.
Most often, I'd say in 60-70% of the cases I can successfully send a GCM message from my server, using the webservice as discussed on google webpages.
Normally, I get the following reply from the webservice, which indicates that I successfully sent the GCM message:
{
"multicast_id":8378088572050307085,
"success":1,
"failure":0,
"canonical_ids":0,
"results":
[
{
"message_id":"0:1363080282442710%7c4250c100000031"
}
]
}
This is saying: all OK, message sent.
However, in many cases I get a HTTP error when calling the webservice, that says:
Unable to read data from the transport connection: An established
connection was aborted by the software in your host machine.
This is the .NET message to tell me that calling a webservice (using HttpWebRequest and POST) failed.
This is some log messages that shows the problem:
This is the code I am using for calling the WS:
public static string SendMessage(string registrationId, string command, string extra, bool retry)
{
try
{
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://android.googleapis.com/gcm/send");
request.Method = PostWebRequest;
request.KeepAlive = false;
GCMPostPacket json = new GCMPostPacket()
{
collapse_key = "1",
time_to_live = 60,
registration_ids = new List<string>(new string[] { registrationId }),
data = new GcmData()
{
message = command,
misc = extra
}
};
// Converting to JSON string
string jsonString = SICJsonProtocol.JSONHelper.Serialize<GCMPostPacket>(json);
byte[] byteArray = Encoding.UTF8.GetBytes(jsonString);
request.ContentType = "application/json";
request.ContentLength = byteArray.Length;
request.ProtocolVersion = HttpVersion.Version10;
request.Headers.Add(HttpRequestHeader.Authorization, "key=" + "MyVerySecretKey");
Stream dataStream = request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
using (WebResponse response = request.GetResponse())
{
HttpStatusCode responseCode = ((HttpWebResponse)response).StatusCode;
if (responseCode.Equals(HttpStatusCode.Unauthorized) || responseCode.Equals(HttpStatusCode.Forbidden))
{
Console.WriteLine("Unauthorized - need new token");
}
else if (!responseCode.Equals(HttpStatusCode.OK))
{
Console.WriteLine("Response from web service not OK :");
Console.WriteLine(((HttpWebResponse)response).StatusDescription);
}
StreamReader reader = new StreamReader(response.GetResponseStream());
string responseLine = reader.ReadLine();
Console.WriteLine("************************");
Console.WriteLine("GCM send: " + responseCode + " | " + responseLine);
// This is the log shown in the image above
SRef.main.gui.ServiceUpdate("GCM send: " + responseCode + " | " + responseLine);
reader.Close();
response.Close();
return responseLine;
}
}
catch (Exception e)
{
// This is the log shown in the image above
SRef.main.gui.ServiceUpdate("Failed send GCM, " + (retry ? "retrying in 20 sec" : "not retrying") + ". Error=" + e.Message);
if (retry)
{
System.Threading.ThreadPool.QueueUserWorkItem(delegate(object obj)
{
try
{
System.Threading.Thread.Sleep(20000);
SendMessage(registrationId, command, extra, false);
}
catch (Exception ex)
{
}
});
}
return null;
}
}
Can anyone see if I am doing something wrong, or if I am missing something in general?
Unable to read data from the transport connection: An established connection was aborted by the software in your host machine.
This error has nothing to do with GCM API in particular. It means that your client tried to contact the web service but the connection that was established has been severed in one of the network layers. Depending on where you get this error and the error message it could mean several things.
Your client decided to abort the connection due to a socket timeout. Increase the client read / socket timeout to improve the situation.
A load balancer that sits between the client and the server dropped the connection. Each party thinks that the other one dropped the connection.
Network timeouts are usually a huge pain since it is never clear where the connection was dropped and who dropped it. If increasing timeouts does not help I'd suggest sending the requests through a proxy that can sniff the HTTPS traffic (charles / TCPMON) or using Wireshark to see which packets are being dropped.
Your android app also has GCM monitoring that you can enable on the Statistics tab. Check if the GCM API reports a status message other than 200 OK on that graph. That will help narrow down the problem further. If there are no reports of status codes other than 200, it means GCM never got your API requests to begin with.
Related
I currently try to write a android app to setup and controll a ESP8266 on which micropython runs.
On the micropython server I initialize a websocket like this:
def __init__(self, task_manager, setup_mode):
address = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
self._socket = socket.socket()
self._socket.bind(address)
self._socket.listen(1)
self._socket.setblocking(False)
self._socket.settimeout(5)
self._task_manager = task_manager
self._setup_mode = setup_mode
print('New Socket is listening on: ', address)
And then simple listen to incoming connections like this, and then react to the incoming messages. Also the listing is looped to allow the microcontroller logic to update every 5 seconds.
client, address = self._socket.accept()
print("New request from:", address)
Everything is working fine when I send test request using python from my PC. For example a simple request would be something like this:
data = json.dumps({'load': {'type': "is_lighthub", 'data': {}}})
response = requests.post(ip, json=data)
However when I try to make the same post request using OkHttp from an android app, then there is no incoming connection at the ESP.
Here is the android java code:
private void addIfLighthub(final InetAddress address) {
try {
RequestBody body = RequestBody.create(JSON, "{\"load\": {\"type\": \"is_lighthub_server\", \"data\": {}}");
Request request = new Request.Builder()
.url("http://" + address.getHostAddress())
.post(body)
.build();
try (Response response = client.newCall(request).execute()) {
final JSONObject myResponse = new JSONObject(response.body().string());
if((boolean)myResponse.get("is_lighthub")) {
onlineDeviceList.add(address);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}
} catch (JSONException jsonException) {
System.out.println(jsonException.getMessage());
}
}
The odd thing however is that that sample code, if provided with for example the address of my router, does receive the routers default html site ...
So, am I missing something? I fairly new to networking but a simple post request from the phone should be the same as from a python sample code, right?
Or is there a error in my java function?
Thank you guys in advance for the help!
If fixed it myself!
The mistake was that the python test client sended the json seperate, while the okhttp client sended both in one piece.
That made the server timeout while waiting for a second message ...
I'm trying to send notifications on my server to my Android device. I'm using Firebase Cloud Messaging for sending the notifications. I can send notifications via the Firebase Console and I receive the message on my phone. However, I'm trying to send a message via my server, which isn't working yet.
I'm getting the following response when I execute the code below:
"{\"message_id\":58934758934758936346437}"
When we look into the documentation of Firebase right here Firebase Documentation, we can see that receiving a message_id implies that the message has been send succesfully. I'm not receiving it on my phone though.
I did subscribe the app to the right topic.
I'm running the following code:
private void test(String topic) {
try {
//Setup request
URL url = new URL("https://fcm.googleapis.com/fcm/send");
HttpURLConnection hc = (HttpURLConnection) url.openConnection();
hc.setDoOutput(true);
hc.setDoInput(true);
//Set request params
String message = "{\"to\": \"/topics/" + topic + "\"}";
hc.addRequestProperty("Content-Type", "application/json");
hc.addRequestProperty("Authorization", "key=SECRET");
DataOutputStream dos = new DataOutputStream(hc.getOutputStream());
dos.writeBytes(message);
dos.close();
//Get Response
InputStream is = hc.getInputStream();
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
StringBuilder response = new StringBuilder(); // or StringBuffer if Java version 5+
String line;
while ((line = rd.readLine()) != null) {
response.append(line);
}
rd.close();
label.setText("RESPONSE: " + response.toString());
} catch (Exception ex) {
label.setText("Er ging iets mis");
ex.printStackTrace();
}
}
You're payload does not contain any message.
String message = "{\"to\": \"/topics/" + topic + "\"}";
It only contains the recipient (to), but there is no actual message. Either send a notification or data message payload. Something like this:
String message = "{\"to\": \"/topics/" + topic + "\",
\"notification\" : {
\"title\" : \"sample title\",
\"body\" : \"sample body\"
}
}";
See the available parameters for notification and data here and note that those two message payloads are handled differently. See the Receiving Messages in Android docs for more details.
Is there any way to send Upstream notification message through FCM from one android device to another devices connected with Firebase database.
I know that XMPP server can then receive the upstream messages and send the notifications to the other devices.To receive messages sent with the upstream API i need to implement an XMPP server but there is any other way???
Is there any way to send Upstream notification message through FCM
from one android device to another devices connected with Firebase
database?
Currently it's NOT possible to send messages directly from one device to another.
(or at least it's not possible without introducing a HUGE security vulnerability: more details below)
Full details:
Sending messages to a user device is a pretty serious action!
based on the payload a message can result in spam, phishing, execution of internal methods.
You want this operation to be allowed only be trusted entities, this is why the FCM send API requires the SERVER-API-KEY in the authentication header.
Adding the SERVER-API-KEY in your app code (or communicating it to the app in some other way) IS NOT SAFE. This because apk can be extracted, decompiled, inspected, executed on emulators, executed under debugging and so on.
The best way to implement this today: is to have some sort of server between the two devices:
[DeviceA] -- please send message to B --> [SERVER] -- fcmSendAPI --> [DeviceB]
The server can be as simple as a PHP page, or a more complex XMPP implementation.
An example in Node.js can be found here:
Sending notifications between devices with Firebase Database and Cloud Messaging
Finally, after 2 months of trying to maintain reliable server script myself, I suddenly found OneSignal. It's completely free, supports device-to-device push messages on iOS, Android, WP and browsers.
Hope, I won't get flag for promotion spam, but it's currently the only (and easiest) way to be completely "backendless".
Also, it's completely secure way. Nobody can send push unless he knows special OS user id, which you can store in Firebase Database protected by rules.
UPD: It's not a replacement for Firebase. It has only push service and nothing else
UPD2: Firebase now has Functions, and examples of it usage has sending FCM. You now don't need any other server or service. Read more in official samples https://github.com/firebase/functions-samples
After lots of try finally i got one solution and its work perfectly
Step 1 :Include two library.
compile 'com.squareup.okhttp3:okhttp:3.4.1'
compile 'com.google.firebase:firebase-messaging:9.2.0'
Step 2 : In your MainActivity or from where you want to send notifications.
OkHttpClient mClient = new OkHttpClient();
String refreshedToken = "";//add your user refresh tokens who are logged in with firebase.
JSONArray jsonArray = new JSONArray();
jsonArray.put(refreshedToken);
Step 3: Create one async task which sends notifications to all devices.
public void sendMessage(final JSONArray recipients, final String title, final String body, final String icon, final String message) {
new AsyncTask<String, String, String>() {
#Override
protected String doInBackground(String... params) {
try {
JSONObject root = new JSONObject();
JSONObject notification = new JSONObject();
notification.put("body", body);
notification.put("title", title);
notification.put("icon", icon);
JSONObject data = new JSONObject();
data.put("message", message);
root.put("notification", notification);
root.put("data", data);
root.put("registration_ids", recipients);
String result = postToFCM(root.toString());
Log.d("Main Activity", "Result: " + result);
return result;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String result) {
try {
JSONObject resultJson = new JSONObject(result);
int success, failure;
success = resultJson.getInt("success");
failure = resultJson.getInt("failure");
Toast.makeText(MainActivity.this, "Message Success: " + success + "Message Failed: " + failure, Toast.LENGTH_LONG).show();
} catch (JSONException e) {
e.printStackTrace();
Toast.makeText(MainActivity.this, "Message Failed, Unknown error occurred.", Toast.LENGTH_LONG).show();
}
}
}.execute();
}
String postToFCM(String bodyString) throws IOException {
public static final String FCM_MESSAGE_URL = "https://fcm.googleapis.com/fcm/send";
final MediaType JSON
= MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(JSON, bodyString);
Request request = new Request.Builder()
.url(Url.FCM_MESSAGE_URL)
.post(body)
.addHeader("Authorization", "key=" + "your server key")
.build();
Response response = mClient.newCall(request).execute();
return response.body().string();
}
Step 4 : Call in onclick of your button
btnSend.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
sendMessage(jsonArray,"Hello","How r u","Http:\\google.com","My Name is Vishal");
}
});
I have seen this question posted in a thousand places and I have tried all the solutions that have worked for others but I am not able to get this to work for me. I used this post as my baseline for the project that Im using (Unauthorized when calling Google GCM). I have tried both the API key and the browser key with no success.
Here is my code:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Web;
using System.Net;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.IO;
namespace GCMTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
string BrowserAPIKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
string message = "some test message";
string tickerText = "example test GCM";
string contentTitle = "content title GCM";
string postData = "{ \"registration_ids\": [ \"" + txtRegistrationID.Text + "\" ], \"data\": {\"tickerText\":\"" + tickerText + "\", \"contentTitle\":\"" + contentTitle + "\", \"message\": \"" + message + "\"}}";
string response = SendGCMNotification(BrowserAPIKey, postData);
}
private string SendGCMNotification(string apiKey, string postData, string postDataContentType = "application/json")
{
// from here:
// https://stackoverflow.com/questions/11431261/unauthorized-when-calling-google-gcm
//
// original:
// http://www.codeproject.com/Articles/339162/Android-push-notification-implementation-using-ASP
ServicePointManager.ServerCertificateValidationCallback += new RemoteCertificateValidationCallback(ValidateServerCertificate);
//
// MESSAGE CONTENT
byte[] byteArray = Encoding.UTF8.GetBytes(postData);
//
// CREATE REQUEST
HttpWebRequest Request = (HttpWebRequest)WebRequest.Create("https://android.googleapis.com/gcm/send");
Request.Method = "POST";
Request.KeepAlive = false;
Request.ContentType = postDataContentType;
//Request.Headers.Add(string.Format("Authorization: key={0}", apiKey));
Request.Headers.Add(HttpRequestHeader.Authorization, String.Format("key={0}", apiKey));
Request.ContentLength = byteArray.Length;
//Stream dataStream;
try
{
Stream dataStream = Request.GetRequestStream();
dataStream.Write(byteArray, 0, byteArray.Length);
dataStream.Close();
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
//
// SEND MESSAGE
try
{
WebResponse Response = Request.GetResponse();
HttpStatusCode ResponseCode = ((HttpWebResponse)Response).StatusCode;
if (ResponseCode.Equals(HttpStatusCode.Unauthorized) || ResponseCode.Equals(HttpStatusCode.Forbidden))
{
MessageBox.Show("Unauthorized - need new token");
}
else if (!ResponseCode.Equals(HttpStatusCode.OK))
{
MessageBox.Show("Response from web service isn't OK");
}
StreamReader Reader = new StreamReader(Response.GetResponseStream());
string responseLine = Reader.ReadToEnd();
Reader.Close();
return responseLine;
}
catch (Exception e)
{
MessageBox.Show(e.Message);
}
return "error";
}
public static bool ValidateServerCertificate(
object sender,
X509Certificate certificate,
X509Chain chain,
SslPolicyErrors sslPolicyErrors)
{
return true;
}
}
}
Its erring on this line:
WebResponse Response = Request.GetResponse();
With the following message:
"The remote server returned an error: (401) Unauthorized."
The form allows an input of a device registration id. If I can get this working it will be altered to be more usable in a production environment. The application is running on my local machine right now but will ultimately reside on a remote server.
Any help that I can get would be GREATLY appreciated. I have been reading posts about this for a week now and I haven't been able to make any progress. One thing that I think might be causing this problem is the fact that Im running this locally instead of on the server. Does this request need to come from the address that is listed in the "referer" in the browser key?
Thanks again!
If you are using a Browser Key, you should remove the allowed referrers of that key, so that it would say Any referrer is allowed. This would allow you to send the notifications from a locally run server.
Otherwise, you'll be able to send GCM messages using that Browser Key only from Web Sites that have the domain specified in allowed referrers.
Check on the console (console.developers.google.com) if the API "Google Cloud Messaging for Android" is active.
I had the same problem, I thought it was active and wasn't
I created a simple web application and hosted it on local server (local host) and used the below code to get the authentication id, but it return 403 error. also is there any way to send message to C2DM without application server I mean from desktop application?
here is my code to get authentication key:
string GetToken()
{
HttpWebResponse response = null;
try
{
StringBuilder builder = new StringBuilder();
builder.Append("Email=").Append("MyGmailEmailAddress");
builder.Append("&Passwd=").Append("MyPassword");
builder.Append("&accountType=GOOGLE");
builder.Append("&source=PingMe");
builder.Append("&service=ac2dm");
byte[] bytes = Encoding.UTF8.GetBytes(builder.ToString());
HttpWebRequest request = (HttpWebRequest)WebRequest.Create("https://www.google.com/accounts/ClientLogin");
request.Method = "POST";
request.ContentType ="application/x-www-form-urlencoded";
request.ContentLength = bytes.Length;
Stream stream = request.GetRequestStream();
stream.Write(bytes,0,bytes.Length);
stream.Close();
response = (HttpWebResponse)request.GetResponse();
stream = response.GetResponseStream();
StreamReader reader = new StreamReader(stream);
return reader.ReadToEnd();
}
catch (Exception ex)
{
return ex.Message;
}
finally
{
if(response != null)
response.Close();
}
}
People are strongly recommending a dedicated Google account for C2DM. Don't use your regular GMail.
Also, have you signed up at http://code.google.com/android/c2dm/signup.html ? If yes, try signing up again, with the same Google account. That site is notoriously wonky.