发送消息


发送消息

在可以使用 Amazon Device Messaging (ADM) 将消息发送到应用实例之前,您的服务器必须满足以下条件:

消息负载和唯一性

使用 ADM 时,您可以发送负载大小可达 6KB 的消息。例如,即时消息程序可能会发送一条其负载类似如下的消息:

"data":
   {
   "from":"Sam",
   "message":"Hey, Max.How are you?",
   "time":"10/26/2012 09:10:00"
   }

另外,有些消息可能根本不包含任何负载数据。例如,一条简单的“同步”类型的状态消息,该消息旨在仅向应用或用户通知同步状况,而不传输唯一数据的负载。在此情况下,您可以传入一个空的 data 参数并包含 consolidationKey 参数的值。通过整合键,ADM 可以标识具有冗余负载的同步消息,并尝试仅只将其中的一条消息传送到应用。

"data":{},
"consolidationKey":"Sync"

有时,即使携带负载的消息也不一定是唯一的。在此情况下,如果可能,您也将使用 consolidationKey 参数允许 ADM 整合消息。

"data":
   {
   "message":"Download \"Catcher in the Rye\"",
   "url":"https:\/\/s3.amazon.com\/CatcherInTheRye"
   },
"consolidationKey":"NewDownload"

请求格式

要发送消息,您的服务器组件(下文称为“您的服务器”)需要发出一个类似如下的 HTTP POST 请求:

POST /messaging/registrations/(REGISTRATION_ID_FOR_DESTINATION_APP_INSTANCE)/messages HTTP/1.1
Host: api.amazon.com
Authorization: Bearer (YOUR_ACCESS_TOKEN)
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0
Accept: application/json
X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0

{
    "data":{"key1":"value1","key2":"value2"},
    "consolidationKey":"Some Key",
    "expiresAfter":86400
}

该 POST 请求的前两行一起构成了该请求的 URL。该 URL 必须包含应接收消息的特定应用实例的注册 ID。您先前应当从该应用实例获取了此注册 ID。有关应用如何向您的服务器发送其注册 ID 的示例,请参阅 ADM 开发工具包下载包中包含的 ADM 示例应用。

https://api.amazon.com/messaging/registrations/(REGISTRATION_ID_FOR_DESTINATION_APP_INSTANCE)/messages

该请求本身由两部分组成:标头和消息正文。标头必须包含以下字段:

字段 描述 示例
Authorization ​在此包含您的当前访问令牌。值必须为: Bearer (YOUR_ACCESS_TOKEN) Authorization: Bearer Atc|MQEWYJxEnP3I1ND03ZzbY_NxQkA7Kn7Aioev _OfMRcyVQ4NxGzJMEaKJ8f0lSOiV-yW270o6fnkI
Content-Type 值必须为:application/json Content-Type: application/json
X-Amzn-Type-Version 值必须为:com.amazon.device.messaging.ADMMessage@1.0 X-Amzn-Type-Version: com.amazon.device.messaging.ADMMessage@1.0
Accept 值必须为:application/json Accept: application/json
X-Amzn-Accept-Type 值必须为:com.amazon.device.messaging.ADMSendResult@1.0 X-Amzn-Accept-Type: com.amazon.device.messaging.ADMSendResult@1.0

对于消息正文的内容,您需要提供由字符串构成的 JSONObject,其中包含以下参数。

参数 描述 示例
data 要随消息一起发送的负载数据。该数据必须采用 JSON 格式的键/值对形式,其中键和值必须为 String 值。该数据的总大小不能超过 6KB,包括键和值以及引号(引起它们)、“:”字符(分隔它们)、逗号(分隔键/值对)和左右括号(括起字段)都算在内。键/值对之间的空格不算在负载大小内。如果消息不包含负载数据,如在同步消息中,您可以传入一个空对象,例如 "data":{} "data":{ "from":"Sam", "message":"Hey, Max.How are you?", "time":"10/26/2012 09:10:00" }
consolidationKey 可选值。这是一个任意字符串,用于指示多条消息在逻辑上是相同的,并允许 ADM 丢弃以前入队的消息,以便放入此新的消息。请注意,不保证以前入队的消息不会被送达。整合键的长度不能超过 64 个字符。 "consolidationKey":"SyncNow"
expiresAfter 可选值。在设备脱机的情况下 ADM 应保留消息的秒数。在此时间过后,消息可能会被丢弃。​允许值的范围为 60(1 分钟)到 2678400(31 天)(含 60 和2678400)。默认值为 604800(1 周)。 "expiresAfter":86400
md5 可选值。这是 data 参数的 base-64 编码 MD5 校验和。如果您为 md5 参数提供值,ADM 会验证其准确性。如果您未提供值,服务器将代表您计算该值。无论哪种情况下,服务器都会将 md5 参数的值一直传递到设备,并在 x-amzn-data-md5 响应标头中返回计算的值。 请参阅“ADM Sample Code”文件夹中计算 MD5 校验和的示例。

为确保 md5 值与 ADM 服务器中的该值相匹配,您应为此参数计算一个值,如下所示:

  1. 使用 UTF-8 代码基于单元的密钥比较对消息的键/值对进行排序。

  2. 通过以下格式将一系列键/值对连接起来:"key1:value1,key2:value2"

  3. 删除键/值对之间的所有空格。“:”字符与任一键或值之间不得有空格。每个键/值对与“,”字符之间也不得有空格。

  4. 按照 RFC 1321 中定义的算法,使用所生成字符串的 UTF-8 字节计算 SHA-256 值。

  5. Base-64 对该计算的 128 位输出进行编码。

响应格式

在成功接收并解释您的 POST 请求消息之后,ADM 服务器会发送一条类似如下的 HTTP 响应消息:

HTTP/1.1 200
X-Amzn-Data-md5: t5psxALRTM7WN30Q8f20tw==
X-Amzn-RequestId: e8bef3ce-242e-11e2-8484-47f4656fc00d
Content-Type: application/json
X-Amzn-Type-Version: com.amazon.device.messaging.ADMSendResult@1.0
Content-Length: 308

{"registrationID":"amzn1.adm-registration.v1.Y29tLmFtYXpvbi5EZXZpY2VNZXNzYWdpbmcu
YL3FOMUlCWEdpdm5TZ3RWbm9XUT0hN0lrSU1YUlNSVVBpT2pOd0lnWktvUT09"}

如果该消息被接受并入队以便传送到设备,ADM 会返回 200 状态代码。对于 200 代码,响应消息在 JSONObject 中包含以下参数:

  • registrationID: 应用实例的当前注册 ID。如果此值不同于您服务器传入的值,则您的服务器必须更新其记录才能使用此值。

如果该消息未被成功接受,ADM 会返回错误(非 200)状态代码。对于非 200 代码,响应消息可能在 JSONObject 的正文中包含以下参数:

  • reason: ​不接受请求的原因。

下表介绍了可能的 ADM 错误状态代码。

代码 描述 示例
400 输入的某些参数无效。可能的状态代码包括: InvalidRegistrationIdInvalidDataInvalidConsolidationKeyInvalidExpirationInvalidChecksumInvalidTypeUnregisteredUnregistered 表示与注册 ID 关联的应用实例不再可用于接收消息。如果其中注册了应用实例的设备的所有者发生了更改,或者应用实例已请求不再接收消息,您可能会收到 Unregistered 状态代码。InvalidRegistrationId 表示注册 ID 不响应由所提供的访问令牌标识的发送者。 "reason": "InvalidRegistrationId"
401 所提供的访问令牌无效。发送者应刷新其访问令牌。有关刷新访问令牌的信息,请参阅请求访问令牌 "reason":"AccessTokenExpired"
413 data 参数中提供的消息负载已超出允许的最大数据大小 (6 KB)。 "reason":"MessageTooLarge"
429 请求者已超出消息的最大允许速率。发送者可能会按照响应中包含的 Retry-After 标头稍后重试。为确保高可用性,ADM 会限制在给定时间段内可发送的消息数量。如果您有特定的容量要求,请联系我们,并在您的请求中提供以下信息:姓名、公司名称、电子邮件地址、请求的 TPS(每秒事务数)限制、原因。 "reason":"MaxRateExceeded"
500 出现内部服务器错误。请求者必须按照响应中包含的 Retry-After 标头稍后重试。 不适用
503 服务器暂时不可用。请求者必须按照响应中包含的 Retry-After 标头稍后重试。 不适用

响应标头包含以下字段:

字段 描述 示例
X-Amzn-Data-md5 数据字段的已计算 base-64 编码 MD5 校验和。 X-Amzn-Data-md5: t5psxALRTM7WN30Q8f20tw==
X-Amzn-RequestId 由 ADM 创建的唯一标识请求的一个值。一旦您在使用 ADM 时遇到问题,亚马逊可以使用此值来解决问题。 X-Amzn-RequestId: b55857e7-242d-11e2-8484-47f4656fc00d
Retry-After 对于 429、500 或 503 错误响应,将返回此字段。Retry-After 指定预计服务多长时间不可用。此值可以为一个从响应时算起的整数秒数(采用十进制),也可以为 HTTP 格式的日期。有关此值可能的格式,请参阅 HTTP/1.1 规范第 14.37 节 Retry-After: 120
Content-Type 资源的内容类型:application/json Content-Type: application/json
X-Amzn-Type-Version 描述响应的格式。 X-Amzn-Type-Version: com.amazon.device.messaging.ADMSendResult@1.0

发出消息请求并处理响应

以下代码示例举例说明了您的服务器软件如何发出发送消息的请求,然后处理 ADM 服务器的响应:

    /**
     * Request that ADM deliver your message to a specific instance of your app.
     */
    public void sendMessageToDevice(String registrationId, String accessToken) throws Exception
    {
        // JSON payload representation of the message.
        JSONObject payload = new JSONObject();

        // Define the key/value pairs for your message content and add them to the
        // message payload.
        JSONObject data = new JSONObject();
        data.put("firstKey", "firstValue");
        data.put("secondKey", "secondValue");
        payload.put("data", data);

        // Add a consolidation key.If multiple messages are pending delivery for a particular
        // app instance with the same consolidation key, ADM will attempt to delivery the most
        // recently added item.
        payload.put("consolidationKey", "ADM_Enqueue_Sample");

        // Add an expires-after value to the message of 1 day.If the targeted app instance does not
        // come online within the expires-after window, the message will not be delivered.
        payload.put("expiresAfter", 86400);

        // Convert the message from a JSON object to a string.
        String payloadString = payload.toString();

        // Establish the base URL, including the section to be replaced by the registration
        // ID for the desired app instance.Because we are using String.format to create
        // the URL, the %1$s characters specify the section to be replaced.
        String admUrlTemplate = "https://api.amazon.com/messaging/registrations/%1$s/messages";

        URL admUrl = new URL(String.format(admUrlTemplate,registrationId));

        // Generate the HTTPS connection for the POST request.You cannot make a connection
        // over HTTP.
        HttpsURLConnection conn = (HttpsURLConnection) admUrl.openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        // Set the content type and accept headers.
        conn.setRequestProperty("content-type", "application/json");
        conn.setRequestProperty("accept", "application/json");
        conn.setRequestProperty("X-Amzn-Type-Version", "com.amazon.device.messaging.ADMMessage@1.0");
        conn.setRequestProperty("X-Amzn-Accept-Type", "com.amazon.device.messaging.ADMSendResult@1.0");

        // Add the authorization token as a header.
        conn.setRequestProperty("Authorization", "Bearer " + accessToken);

        // Obtain the output stream for the connection and write the message payload to it.
        OutputStream os = conn.getOutputStream();
        os.write(payloadString.getBytes(), 0, payloadString.getBytes().length);
        os.flush();
        conn.connect();

        // Obtain the response code from the connection.
        int responseCode = conn.getResponseCode();

        // Check if we received a failure response, and if so, get the reason for the failure.
        if( responseCode != 200)
        {
            if( responseCode == 401 )
            {
                // If a 401 response code was received, the access token has expired.The token should be refreshed
                // and this request can be retried.
            }

            String errorContent = parseResponse(conn.getErrorStream());
            throw new RuntimeException(String.format("ERROR: The enqueue request failed with a " +
                                             "%d response code, with the following message: %s",
                                             responseCode, errorContent));
        }
        else
        {
            // The request was successful.The response contains the canonical Registration ID for the specific instance of your
            // app, which might be different that the one used for the request.

            String responseContent = parseResponse(conn.getInputStream());
            JSONObject parsedObject = new JSONObject(responseContent);

            String canonicalRegistrationId = parsedObject.getString("registrationID");

            // Check if the two Registration IDs are different.
            if(!canonicalRegistrationId.equals(registrationId))
            {
                // At this point the data structure that stores the Registration ID values should be updated
                // with the correct Registration ID for this particular app instance.
            }
        }

    }

    private String parseResponse(InputStream in) throws Exception
    {
        // Read from the input stream and convert into a String.
        InputStreamReader inputStream = new InputStreamReader(in);
        BufferedReader buff = new BufferedReader(inputStream);

        StringBuilder sb = new StringBuilder();
        String line = buff.readLine();
        while(line != null)
       {
          sb.append(line);
          line = buff.readLine();
        }

        return sb.toString();
    }