发送消息


发送消息

在可以使用 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 服务器的响应:

    /**
     * 请求 ADM 将消息传递到您的应用程序的特定实例。
     */
    public void sendMessageToDevice(String registrationId, String accessToken) throws Exception
    {
        // 消息的 JSON 有效负载表示。
        JSONObject payload = new JSONObject();

        // 为您的消息内容定义键/值对,并将它们添加到
        // 消息有效负载。
        JSONObject data = new JSONObject();
        data.put("firstKey", "firstValue");
        data.put("secondKey", "secondValue");
        payload.put("data", data);

        // 添加一个整合键。如果多条消息正在等待传递给
        // 具有相同的整合键的特定应用实例,则 ADM 将会尝试传递
        // 最近添加的项目。
        payload.put("consolidationKey", "ADM_Enqueue_Sample");

        // 向消息添加值为 1 天的 expires-after 值。如果目标应用实例
        // 在 expires-after 时间期限内未联机,则不会传递消息。
        payload.put("expiresAfter", 86400);

        // 将 JSON 对象中的消息转换为字符串。
        String payloadString = payload.toString();

        // 建立基本 URL,包括要被所需的应用实例的
        // 注册 ID 替换的部分。因为我们使用 String.format 创建
        // URL,因此 %1$s 字符指定要替换的部分。
        String admUrlTemplate = "https://api.amazon.com/messaging/registrations/%1$s/messages";

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

        // 为 POST 请求生成 HTTPS 连接。您无法通过 HTTP 建立
        // 连接。
        HttpsURLConnection conn = (HttpsURLConnection) admUrl.openConnection();
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        // 设置内容类型和接受标头。
        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");

        // 添加授权令牌作为标头。
        conn.setRequestProperty("Authorization", "Bearer " + accessToken);

        // 获取连接的输出流并写入消息有效负荷。
        OutputStream os = conn.getOutputStream();
        os.write(payloadString.getBytes(), 0, payloadString.getBytes().length);
        os.flush();
        conn.connect();

        // 从连接获取响应代码。
        int responseCode = conn.getResponseCode();

        // 检查我们是否收到了失败响应,如果是,则获取失败的原因。
        if( responseCode != 200)
        {
            if( responseCode == 401 )
            {
                // 如果收到 401 响应代码,则表明访问令牌已过期。令牌应刷新
                // 并且可以重试此请求。
            }

            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
        {
            // 请求成功。响应包含您的应用的特定实例的规范注册 ID,
            // 它与用于请求的 ID 可能不同。

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

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

            // 检查两个注册 ID 是否不同。
            if(!canonicalRegistrationId.equals(registrationId))
            {
                // 此时应使用此特定应用实例的正确注册 ID 来
                // 更新存储注册 ID 值的数据结构。
            }
        }

    }

    private String parseResponse(InputStream in) throws Exception
    {
        // 从输入流中读取并转换为字符串。
        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();
    }