将 AWS Lex v2 流与 C++ 结合使用:签名不匹配

Using AWS Lex v2 streaming with C++: Signature does not match

我正在尝试使我 PC 上的 C++ 应用程序使用流式 API 与 AWS Lex v2 通信。但是哪里都绝对没有示例代码,逼着我一路猜测流程。我设法将一些东西缝合在一起,但没有用。我收到以下错误:

在请求处理程序中: CRC 不匹配。 prelude_crc 为 0x0865223A22,但计算为 0x08A98FE9C5 在响应处理程序中:我们计算的请求签名与您提供的签名不匹配。检查您的 AWS 秘密访问密钥和签名方法。有关详细信息,请参阅服务文档。

秘密访问密钥没有任何问题,因为我可以使用 aws lexv2-runtime recognize-text 命令从同一台 PC 从 AWS CLI 调用同一个机器人。

是否需要先使用签名为流媒体播种?

AWS 日志说明如下:

[ERROR] 2021 - 08 - 19 18:08 : 19.427 AWSClient[24956] HTTP response code : -1
Resolved remote host IP address :
Request ID :
Exception name :
Error message : Encountered network error when sending http request
0 response headers :
[WARN] 2021 - 08 - 19 18:08 : 19.427 AWSClient[24956] If the signature check failed.This could be because of a time skew.Attempting to adjust the signer.
[WARN] 2021 - 08 - 19 18:08 : 19.427 AWSClient[24956] Request failed, now waiting 50 ms before attempting again.
[WARN] 2021 - 08 - 19 18:08 : 19.489 WinHttpSyncHttpClient[24956] Failed setting TCP keep - alive interval with error code : 12018
[WARN] 2021 - 08 - 19 18:08 : 19.717 AWSErrorMarshaller[24956] Encountered AWSError 'InvalidSignatureException' : The request signature we calculated does not match the signature you provided.Check your AWS Secret Access Key and signing method.Consult the service documentation for details.
[ERROR] 2021 - 08 - 19 18:08 : 19.717 AWSClient[24956] HTTP response code : 403

我的整个测试代码如下,我漏了什么?既然我可以确认 key 和 secret 没问题,那么如何确保它被正确签名呢? BOT 区域是 us-east-1,但我正在从另一个区域发出请求。这重要吗?

#include <aws/core/Aws.h>
#include <aws/core/client/AsyncCallerContext.h>
#include <aws/core/utils/Outcome.h>
#include <aws/core/auth/AWSCredentialsProvider.h>
#include <aws/lexv2-runtime/LexRuntimeV2Client.h>
#include <aws/lexv2-runtime/model/AudioInputEvent.h>
#include <aws/lexv2-runtime/model/RecognizeUtteranceRequest.h>
#include <aws/lexv2-runtime/model/RecognizeUtteranceResult.h>
#include <aws/lexv2-runtime/model/StartConversationHandler.h>
#include <aws/lexv2-runtime/model/StartConversationRequest.h>
#include <aws/lexv2-runtime/model/StartConversationRequestEventStream.h>
#include <aws/lex/LexRuntimeServiceClient.h>
#include <aws/lex/LexRuntimeServiceRequest.h>
#include <aws/lex/model/PostContentRequest.h>
#include <aws/lex/model/PostContentResult.h>
#include <iterator>
#include <nlohmann_json.hpp>
#include <fstream>
#include <chrono>

using namespace Aws::LexRuntimeV2;
int SessionCount = 0;

int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cout << "Usage: LexV2Test pcm_audio.raw bot_name_cred.json" << std::endl;
        return -1;
    }

    // read json file and populate the fields
    std::ifstream jsonFile(argv[1]);
    if (!jsonFile)
    {
        std::cout << "Cannot open license file " << argv[1] << std::endl;
        return -1;
    }

    nlohmann::json jsonObj = nlohmann::json::parse(std::istreambuf_iterator<char>(jsonFile), std::istreambuf_iterator<char>());
    jsonFile.close();

    std::string lexKey, lexSecret;
    std::string botId, botAliasId, localeId, sessionId, regionId;

    botId = jsonObj["bot_id"];
    botAliasId = jsonObj["bot_alias_id"];
    lexKey = jsonObj["lex_key"];
    lexSecret = jsonObj["lex_secret"];
    localeId = jsonObj["lex_locale"];

    Aws::SDKOptions options;
    options.loggingOptions.logLevel = Aws::Utils::Logging::LogLevel::Info;
    Aws::InitAPI(options);
    Aws::Client::ClientConfiguration config;
    config.region = jsonObj["lex_region"];

    auto lexClient = Aws::MakeUnique<LexRuntimeV2Client>("MyClient", Aws::Auth::AWSCredentials(lexKey.c_str(), lexSecret.c_str()), config);
    Model::StartConversationRequest LexRequest;
    Model::StartConversationHandler requestHandler;

    requestHandler.SetTranscriptEventCallback([](const Model::TranscriptEvent& ev)
        {
            std::cout << ev.GetTranscript() << std::endl;
        });
    requestHandler.SetOnErrorCallback([](const Aws::Client::AWSError<LexRuntimeV2Errors>& error)
        {
            std::cout << "Request handler: " << error.GetMessage() << std::endl;
        });


    LexRequest.WithLocaleId(localeId).WithBotId(botId.c_str()).WithBotAliasId(botAliasId.c_str()).WithSessionId("Blah")
        .WithConversationMode(Model::ConversationMode::AUDIO).WithEventStreamHandler(requestHandler);

    Aws::Utils::Threading::Semaphore signaling(0 /*initialCount*/, 1 /*maxCount*/);
    auto OnResponseCallback = [&signaling](const LexRuntimeV2Client*,const Model::StartConversationRequest&,
        const Model::StartConversationOutcome& outcome, const std::shared_ptr<const Aws::Client::AsyncCallerContext>&) 
    { 
        std::cout << "Response handler: " << outcome.GetError().GetMessage();
        signaling.Release(); 
    };

    Model::StartConversationRequestEventStream* pStream = nullptr;
    Aws::Utils::Threading::Semaphore starting(0 /*initialCount*/, 1 /*maxCount*/);
    auto OnStreamReady = [&starting,&pStream](Model::StartConversationRequestEventStream& stream)
    {
        pStream = &stream;
        pStream->SetSignatureSeed("blah");
        starting.Release();
    };

    lexClient->StartConversationAsync(LexRequest, OnStreamReady, OnResponseCallback, nullptr);
    starting.WaitOne();
    std::ifstream audioFile(argv[2], std::ios_base::binary);
    if (!audioFile)
    {
        std::cout << "Cannot open audio file " << argv[2] << std::endl;
        return 0;
    }

    while (!audioFile.eof())
    {
        unsigned char buf[320 + 1];
        audioFile.read((char*)buf, 320);
        Aws::Utils::ByteBuffer bytes(buf, audioFile.gcount());
        Model::AudioInputEvent input;
        auto millisec_since_epoch = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock().now().time_since_epoch()).count();
        input.SetClientTimestampMillis(millisec_since_epoch);
        input.SetAudioChunk(bytes);
        input.SetContentType("audio/lpcm; sample-rate=8000; sample-size-bits=16; channel-count=1; is-big-endian=false");
        pStream->WriteAudioInputEvent(input);
        _sleep(20);
    }
    signaling.WaitOne(); // prevent the application from exiting until we're done

    Aws::ShutdownAPI(options);
    return 0;
}

原来这是 Windows 上 WinHttp/WinINet 的限制。它还不完全支持 HTTP/2 多路复用。所以我切换到 CURL,它起作用了。

由于 AWS streaming lex/transcribe 功能需要 HTTP/2 多路复用,请始终将其与 CURL 一起使用。