理论上可以通过单一的http POST请求在Kinesis中使用`putRecord`吗?

时间:2018-06-14 00:05:27

标签: amazon-web-services aws-sdk amazon-kinesis

我一直在使用AWS Kinesis遇到一些问题,因为我设置了一个流,并且我想使用标准的http POST请求来调用我的流上的Kinesis PutRecord调用。我之所以这样做,是因为我生成的javascript应用程序的包大小很重要,我宁愿不导入aws-sdk来完成应该(在纸面上)可能的事情。

就在你知道的时候,我已经看过this other stack overflow question about the same thing了,这是......有点信息。

现在,我已经有了一种使用访问密钥,秘密令牌和会话令牌对sigv4签名请求的方法。但是当我最终得到签名请求的结果并使用浏览器中的fetch api发送它时,服务坦克(或者json对象引用相同的东西,取决于我的Content-Type标题,我猜测)结果。

这是我正在使用的代码

// There is a global function "sign" that does sigv4 signing
// ...

var payload = {
    Data: { task: "Get something working in kinesis" },
    PartitionKey: "1",
    StreamName: "MyKinesisStream"
}

var credentials =  {
    "accessKeyId": "<access.key>",
    "secretAccessKey": "<secret.key>",
    "sessionToken": "<session.token>",
    "expiration": 1528922673000
}

function signer({ url, method, data }) {
    // Wrapping with URL for piecemeal picking of parsed pieces
    const parsed = new URL(url);

    const [ service, region ] = parsed.host.split(".");

    const signed = sign({
        method,
        service,
        region,
        url,

        // Hardcoded
        headers : {
            Host           : parsed.host,
            "Content-Type" : "application/json; charset=UTF-8",

            "X-Amz-Target" : "Kinesis_20131202.PutRecord"
        },

        body : JSON.stringify(data),
    }, credentials);

    return signed;
}

// Specify method, url, data body
var signed = signer({
    method: "POST",
    url: "https://kinesis.us-west-2.amazonaws.com",
    data : JSON.stringify(payload)
});

var request = fetch(signed.url, signed);

当我查看请求的结果时,我得到了这个:

{
    Output: { 
       __type: "com.amazon.coral.service#InternalFailure"},
       Version: "1.0" 
}

现在我不确定Kinesis是否真的在这里失败,或者我的输入是否格式不正确?

这是签名请求的样子

{
    "method": "POST",
    "service": "kinesis",
    "region": "us-west-2",
    "url": "https://kinesis.us-west-2.amazonaws.com",
    "headers": {
        "Host": "kinesis.us-west-2.amazonaws.com",
        "Content-Type": "application/json; charset=UTF-8",
        "X-Amz-Target": "Kinesis_20131202.PutRecord",
        "X-Amz-Date": "20180613T203123Z",
        "X-Amz-Security-Token": "<session.token>",
        "Authorization": "AWS4-HMAC-SHA256 Credential=<access.key>/20180613/us-west-2/kinesis/aws4_request, SignedHeaders=content-type;host;x-amz-target, Signature=ba20abb21763e5c8e913527c95a0c7efba590cf5ff1df3b770d4d9b945a10481"
    },
    "body": "\"{\\\"Data\\\":{\\\"task\\\":\\\"Get something working in kinesis\\\"},\\\"PartitionKey\\\":\\\"1\\\",\\\"StreamName\\\":\\\"MyKinesisStream\\\"}\"",
    "test": {
        "canonical": "POST\n/\n\ncontent-type:application/json; charset=UTF-8\nhost:kinesis.us-west-2.amazonaws.com\nx-amz-target:Kinesis_20131202.PutRecord\n\ncontent-type;host;x-amz-target\n508d2454044bffc25250f554c7b4c8f2e0c87c2d194676c8787867662633652a",
        "sts": "AWS4-HMAC-SHA256\n20180613T203123Z\n20180613/us-west-2/kinesis/aws4_request\n46a252f4eef52991c4a0903ab63bca86ec1aba09d4275dd8f5eb6fcc8d761211",
        "auth": "AWS4-HMAC-SHA256 Credential=<access.key>/20180613/us-west-2/kinesis/aws4_request, SignedHeaders=content-type;host;x-amz-target, Signature=ba20abb21763e5c8e913527c95a0c7efba590cf5ff1df3b770d4d9b945a10481"
    }

(测试密钥由生成签名的库使用,因此请忽略它) (还有可能在主体中有额外的斜杠,因为我使用JSON.stringify打印了响应对象)。

我的问题:我有什么遗失的东西吗? Kinesis是否需要标题a,b和c而我只生成两个标题?或者这个内部错误是否真正失败。我输了,因为回答表明我无能为力。

我感谢任何帮助!

编辑:作为第二个问题,我是否正确使用X-Amz-Target标题?这个你引用服务功能的方式,只要你点击那个服务端点,不是吗?

更新:关注迈克尔的评论,我已经到了某个地方,但我仍然无法解决问题。这就是我的所作所为:

我确保在我的payload我只在JSON.stringify属性上运行Data

我还将Content-Type标题修改为"Content-Type" : "application/x-amz-json-1.1",因此我收到了更多有用的错误消息。

现在,我的有效载荷仍大致相同:

var payload = {
    Data: JSON.stringify({ task: "Get something working in kinesis" }),
    PartitionKey: "1",
    StreamName: "MyKinesisStream"
}

我的签名者函数体看起来像这样:

 function signer({ url, method, data }) {
    // Wrapping with URL for piecemeal picking of parsed pieces
    const parsed = new URL(url);

    const [ service, region ] = parsed.host.split(".");

    const signed = sign({
        method,
        service,
        region,
        url,

        // Hardcoded
        headers : {
            Host           : parsed.host,
            "Content-Type" : "application/json; charset=UTF-8",

            "X-Amz-Target" : "Kinesis_20131202.PutRecord"
        },

        body : data,
    }, credentials);

    return signed;
}

所以我传入了一个部分序列化的对象(至少数据是),当我发送给服务时,我收到了响应:

{"__type":"SerializationException"}

至少略微有用,因为它告诉我输入技术上不正确。但是,为了纠正这个问题,我做了一些事情:

  • 我在整个有效负载
  • 上运行JSON.stringify
  • 我已将Data键更改为字符串值以查看是否会通过
  • 我尝试在数据上运行JSON.stringify,然后运行btoa因为我在另一篇文章中读到了对某人有用的内容。

但我仍然遇到同样的错误。我觉得我如此接近。你能发现我可能遗失的任何东西或者我没有尝试过的东西吗?我已经得到了零星的未知操作例外,但我认为现在这个序列化让我感到难过。

编辑2:

事实证明,Kinesis只接受base64编码的字符串。这可能是aws-sdk提供的一种精确性,但基本上所需要的只是有效负载中的Data: btoa(JSON.stringify({ task: "data"}))才能使其正常工作

2 个答案:

答案 0 :(得分:2)

虽然我不确定这是唯一的问题,但似乎您发送的请求正文包含错误的序列化(双重编码)有效负载。

var obj = { foo: 'bar'};

JSON.stringify(obj)返回一个字符串......

'{"foo": "bar"}' // the ' are not part of the string, I'm using them to illustrate that this is a thing of type string.

...当使用JSON解析器解析时,此返回一个对象

{ foo: 'bar' }

但是,JSON.stringify(JSON.stringify(obj))返回不同的字符串......

'"{\"foo\": \"bar\"}"'

...但在解析时,此会返回一个字符串

 '{"foo": "bar"}'

服务端点期望解析主体并获取对象,而不是字符串......因此,解析请求主体(从服务的角度来看)不会返回正确的类型。该错误似乎是服务无法以非常低的级别解析您的请求。

在您的代码中,body: JSON.stringify(data)应该只是body: data,因为之前,您已经使用data: JSON.stringify(payload)创建了一个JSON对象。

如上所述,您正在有效地将body设置为JSON.stringify(JSON.stringify(payload))。

答案 1 :(得分:1)

不确定您是否曾想过,但在搜索操作方法时,此问题会在Google上弹出。我想您缺少的一件事是“记录数据”字段必须是base64编码的。这是一部分NodeJS代码(使用PutRecords可以做到这一点)。

对于任何询问的人,为什么不只使用SDK?目前,由于其他依赖关系,我必须从无法将其更新为SDK所需的NodeJS版本的群集中流式传输数据。是的。

const https = require('https')
const aws4  = require('aws4')
const request = function(o) { https.request(o, function(res) { res.pipe(process.stdout) }).end(o.body || '') }

const _publish_kinesis = function(logs) {
    const kin_logs = logs.map(function (l) {
        let blob = JSON.stringify(l) + '\n'
        let buff = Buffer.from(blob, 'binary');
        let base64data = buff.toString('base64');

        return {
            Data: base64data,
            PartitionKey: '0000'
        }
    })

    while(kin_logs.length > 0) {
        let data = JSON.stringify({
            Records: kin_logs.splice(0,250),
            StreamName: 'your-streamname'
        })

        let _request = aws4.sign({
            hostname: 'kinesis.us-west-2.amazonaws.com',
            method: 'POST',
            body: data,
            path: '/?Action=PutRecords',
            headers: {
                'Content-Type': 'application/x-amz-json-1.1',
                'X-Amz-Target': 'Kinesis_20131202.PutRecords'
            },
         }, {
            secretAccessKey: "****",
            accessKeyId: "****"
           // sessionToken: "<your-session-token>"
         })

        request(_request)
    }
}

var logs = [{
  'timeStamp': new Date().toISOString(),
  'value': 'test02',
},{
  'timeStamp': new Date().toISOString(),
  'value': 'test01',
}]
_publish_kinesis(logs)