.net C# API GW 在发送到 AWS SQS 时触发了 AWS Lambda returns "Connection refused"

.net C# API GW triggered AWS Lambda returns "Connection refused" when sending to AWS SQS

我的简单 .net C# AWS Lambda 函数,它由到 API GW 的 HTTP GET 触发,但在发送到 AWS SQS 时 returns“连接被拒绝”

responseSendMsg = await sqsClient.SendMessageAsync(sendMessageRequest);

似乎是调用 SQS 的问题。

这是完整代码,90% 是由 Visual Studio 2019 年 AWS Toolkit for Visual Studio 生成的,新的 AWS 项目模板“AWS Serverless Application (.NET Core - C#)”,我只有:

  1. 将 lambda 函数处理程序方法签名更新为异步 对于任务,我遵循 https://docs.aws.amazon.com/lambda/latest/dg/csharp-handler.html#csharp-handler-async
  2. 中记录的模式
public async Task<APIGatewayProxyResponse> Get(APIGatewayProxyRequest request, ILambdaContext context)
  1. 在代码中添加写入SQS
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

using Amazon.Lambda.Core;
using Amazon.Lambda.APIGatewayEvents;
using Amazon.SQS;
using TEST.SQS;
using Amazon.SQS.Model;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace TestAsyncFromLambda
{
    public class Functions
    {
        public Functions()
        {
        }

        public async Task<APIGatewayProxyResponse> Get(APIGatewayProxyRequest request, ILambdaContext context)
        {
            Console.WriteLine("Get Request\n");

            // Call a local async method to prove that works, leaving SQS out of the situation for the moment
            await Method1();


            // Create the Amazon SQS client
            var clientConfig = new AmazonSQSConfig
            {
                ServiceURL = SQSConstants.AWS_SERVICE_URL,
            };
            var sqsClient = new AmazonSQSClient(clientConfig);

            // Create and initialize a SendMessageRequest instance
            SendMessageRequest sendMessageRequest = new SendMessageRequest();
            sendMessageRequest.QueueUrl = SQSConstants.MyQueueUrl;
            sendMessageRequest.MessageBody = "this is a test message";

            Console.WriteLine($"About to send message to queue: {sendMessageRequest.QueueUrl}");

            // Send the SQS message using "await"
            SendMessageResponse responseSendMsg = null;
            try
            {
                responseSendMsg =
                await sqsClient.SendMessageAsync(sendMessageRequest);
                //responseSendMsg =
                //sqsClient.SendMessageAsync(sendMessageRequest).GetAwaiter().GetResult(); ;
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw e;
            }

            Console.WriteLine("SendMessage() complete");

            var response = new APIGatewayProxyResponse
            {
                StatusCode = (int)HttpStatusCode.OK,
                Body = "Hello AWS Serverless",
                Headers = new Dictionary<string, string> { { "Content-Type", "text/plain" } }
            };

            return response;
        }

        public static async Task Method1()
        {
            await Task.Run(() =>
            {
                int iterations = 3;
                for (int i = 1; i <= iterations; i++)
                {
                    Console.WriteLine(" Method 1, i=" + i + " of " + iterations);
                    // Do something
                    Task.Delay(100).Wait();
                }
            });
        }
    }
}

显然这是一个简化的示例,我计划在该函数中进行大量 SQS 处理,并希望使用异步会有所帮助。

云监视日志显示函数已被触发,到达发送到 SQS 的调用,然后抛出异常:

System.Net.Http.HttpRequestException: Connection refused
2021-06-24T16:13:54.751+10:00   START RequestId: MyID-91a3-f1e61c3c3f7a Version: $LATEST
2021-06-24T16:13:55.240+10:00   Get Request
2021-06-24T16:13:55.261+10:00   Method 1, i=1 of 3
2021-06-24T16:13:55.380+10:00   Method 1, i=2 of 3
2021-06-24T16:13:55.481+10:00   Method 1, i=3 of 3
2021-06-24T16:13:56.160+10:00   About to send message to queue: https://sqs.ap-southeast-2.amazonaws.com/MyID/MyQueue.fifo
2021-06-24T16:14:32.503+10:00   System.Net.Http.HttpRequestException: Connection refused
2021-06-24T16:14:32.503+10:00   ---> System.Net.Sockets.SocketException (111): Connection refused
2021-06-24T16:14:32.503+10:00   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   --- End of inner exception stack trace ---
2021-06-24T16:14:32.503+10:00   at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.HttpWebRequestMessage.GetResponseAsync(CancellationToken cancellationToken)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.HttpHandler`1.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.Unmarshaller.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.SQS.Internal.ValidationResponseHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.ErrorHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.EndpointDiscoveryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CredentialsRetriever.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.RetryHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.CallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.ErrorCallbackHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at Amazon.Runtime.Internal.MetricsHandler.InvokeAsync[T](IExecutionContext executionContext)
2021-06-24T16:14:32.503+10:00   at TestAsyncFromLambda.Functions.Get(APIGatewayProxyRequest request, ILambdaContext context) in 

我相信我使用的都是最新版本:

我还尝试了一些 C# async await 语法的其他排列,结果相同。

responseSendMsg = sqsClient.SendMessageAsync(sendMessageRequest).GetAwaiter().GetResult(); ;

API GW、Lambda 或 SQS 不涉及 VPC,一切都在同一个 AWS 账户中,只是基本的简单设置。

我还做了一个测试,我将 Lambda 发送到的 SQS 中的队列 URL 更改为一个不存在的无效队列名称,我得到了相同的行为,所以这意味着可能SQS 发送请求甚至从未到达 AWS。

对于适用于 SQS 的 AWS .net SDK,在使用 SQS 的同步和异步方法之间没有选择,只有异步:https://docs.aws.amazon.com/sdk-for-net/latest/developer-guide/SendMessage.html

Lambda 函数附加到具有这些权限的角色,我认为这应该足够了: 我什至添加了“AdministratorAccess”角色,所以我假设只要 Lambda 和 SQS 在同一个 AWS 账户上,Lambda 就可以访问 SQS。

文件 aws-lambda-tools-defaults.json 有:

{
    "Information" : [
        "This file provides default values for the deployment wizard inside Visual Studio and the AWS Lambda commands added to the .NET Core CLI.",
        "To learn more about the Lambda commands with the .NET Core CLI execute the following command at the command line in the project root directory.",
        "dotnet lambda help",
        "All the command line options for the Lambda command can be specified in this file."
    ],
    "profile"     : "myProfile",
    "region"      : "ap-southeast-2",
    "configuration" : "Release",
    "framework"     : "netcoreapp3.1",
    "s3-prefix"     : "TestAsyncFromLambda/",
    "template"      : "serverless.template",
    "template-parameters" : "",
    "s3-bucket"           : "awsserverless2stack-bucket-myID",
    "stack-name"          : "TestAsyncFromLambda"
}

文件serverless.template是:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Transform": "AWS::Serverless-2016-10-31",
  "Description": "An AWS Serverless Application by matt.",
  "Resources": {
    "Get": {
      "Type": "AWS::Serverless::Function",
      "Properties": {
        "Handler": "TestAsyncFromLambda::TestAsyncFromLambda.Functions::Get",
        "Runtime": "dotnetcore3.1",
        "CodeUri": "",
        "MemorySize": 256,
        "Timeout": 187,
        "Role": null,
        "Policies": [
          "AWSLambdaBasicExecutionRole",
          "AmazonSQSFullAccess",
          "AdministratorAccess"
        ],
        "Events": {
          "RootGet": {
            "Type": "Api",
            "Properties": {
              "Path": "/",
              "Method": "GET"
            }
          }
        }
      }
    }
  },
  "Outputs": {
    "ApiURL": {
      "Description": "API endpoint URL for Prod environment",
      "Value": {
        "Fn::Sub": "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
      }
    }
  }
}

我已经尝试通过 AWS 控制台 Web UI 和 Cloud Formation 脚本手动创建队列,示例取自 here:

{
  "AWSTemplateFormatVersion": "2010-09-09",
  "Resources": {
    "MyQueue": {
      "Properties": {
        "QueueName": "MyQueue.fifo",
        "FifoQueue": true,
        "ContentBasedDeduplication": true
      },
      "Type": "AWS::SQS::Queue"
    }
  },
  "Outputs": {
    "QueueName": {
      "Description": "The name of the queue",
      "Value": {
        "Fn::GetAtt": [
          "MyQueue",
          "QueueName"
        ]
      }
    },
    "QueueURL": {
      "Description": "The URL of the queue",
      "Value": {
        "Ref": "MyQueue"
      }
    },
    "QueueARN": {
      "Description": "The ARN of the queue",
      "Value": {
        "Fn::GetAtt": [
          "MyQueue",
          "Arn"
        ]
      }
    }
  }
}

我将 lambda 超时增加到一个很长的自定义值:

  1. 将该持续时间的超时与其他超时区分开来
  2. 为 SQS 发送重试提供更多时间and/or超时

我通过将 serverless.template 更改为:

来增加超时
"Timeout": 187,

在增加超时之前,当它是默认的 30 秒时,我在云监视日志中没有异常,就像 Lambda 超时一样。

正如我在评论中提到的,看起来用于 AWS 服务 URL 和队列 URL 的 URL 值不正确。这就是发送消息时连接失败的原因。

因此您需要确保 SQSConstants.AWS_SERVICE_URLSQSConstants.MyQueueUrl 具有正确的值。