【问题标题】:How to invoke an AWS Step Function using API Gateway如何使用 API Gateway 调用 AWS Step Function
【发布时间】:2017-08-12 08:58:12
【问题描述】:

如何使用 API Gateway POST 请求调用 AWS Step Function,并将请求的 JSON 有效负载发送到 Step Function?

【问题讨论】:

    标签: amazon-web-services aws-api-gateway aws-step-functions


    【解决方案1】:

    1。创建你的阶梯函数

    很明显。我想如果你正在阅读这篇文章,你就会知道如何去做。

    否则,您可以在这里查看文档:What is AWS Step Functions?


    2。为您的 API 创建 IAM 角色

    它可以用于所有 Step Functions,也可以仅用于这一个。我们将只介绍第一种情况,如亚马逊教程中所述:Creating an API Using API Gateway

    创建 IAM 角色

    • 登录 AWS Identity and Access Management 控制台。

    • 在“角色”页面上,选择“创建新角色”。

    • 在“设置角色名称”页面上,为角色名称键入 APIGatewayToStepFunctions,然后选择“下一步”。

    • 在“选择角色类型”页面的“选择角色类型”下,选择 Amazon API Gateway。

    • 在附加策略页面上,选择下一步。

    • 在 Review 页面上,记下 Role ARN,例如:

    • arn:aws:iam::123456789012:role/APIGatewayToStepFunctions

    • 选择创建角色。

    将策略附加到 IAM 角色

    • 在“角色”页面上,按名称搜索您的角色 (APIGatewayToStepFunctions),然后选择角色。
    • 在“权限”选项卡上,选择“附加策略”。
    • 在附加策略页面上,搜索 AWSStepFunctionsFullAccess,选择策略,然后选择附加策略。

    3。设置

    3.a 如果您没有 JSON 有效负载

    正如 Ka Hou Ieong 在How can i call AWS Step Functions by API Gateway? 中所解释的,您可以通过 API Gateway 控制台创建 AWS 服务集成,如下所示:

    • 集成类型:AWS 服务
    • AWS 服务:Step Functions
    • HTTP 方法:POST
    • 动作类型:使用动作名称
    • 操作:开始执行
    • 执行角色:开始执行的角色(我们刚刚创建的那个。只需粘贴它的 ARN)
    • 标题:

      X-Amz-Target -> 'AWSStepFunctions.StartExecution'
      内容类型 -> 'application/x-amz-json-1.0'

    • 正文映射模板/请求负载:

      {
          "input": "string" (optional),
          "name": "string" (optional),
          "stateMachineArn": "string"
      }
      

    3.b 如果您确实有 JSON 有效负载作为输入传递

    一切都与 2.a 中的相同,除了身体映射模板。你要做的就是把它变成一个字符串。使用 $util.escapeJavascript(),例如这样。它会将您的整个请求的正文作为输入传递给您的 Step Function

        #set($data = $util.escapeJavaScript($input.json('$')))
        {
            "input": "$data",
            "name": "string" (optional),
            "stateMachineArn": "string" (required)
        }
    

    注意事项

    • stateMachineArn:如果您不想将 stateMachineArn 作为请求的一部分传递给 API 网关,您可以简单地将其硬编码到您的身体映射模板中(请参阅 AWS API Gateway with Step Function
    • name:省略 name 属性将使 API Gateway 在每次执行时为您生成一个不同的属性。

    现在,这是我的第一个“回答你自己的问题”,所以也许不是这样,但我确实花了好几个小时试图了解我的映射模板出了什么问题。希望这将有助于节省其他人的头发和时间。

    【讨论】:

    • 请注意,StepFunction 执行的最大有效负载大小为 32K,因此如果您的主体大于此,我建议 API 调用 Lambda 将主体存储在 S3 中,然后执行 StepFunction ,传入 S3 存储桶/密钥。使 API 逻辑更简单,并且不易受可变数据大小的影响。
    • 完全正确。并且这个限制也在 StepFunction 中强制执行(当一个步骤需要从源下载图片时),以同样的方式解决。我应该更新我的答案吗?
    • @ElFitz 推荐您的帖子对我帮助很大。在第 3.b 节中,我想用动态填充区域和帐户 ID 来形成 stateMachineArn。与我的情况一样,Stepfunction 和 API Gateway 位于同一区域和帐户 ID 中。我能够从 $context.accountId 获取帐户 ID,但找不到任何提供当前区域名称的内容。在我的模板 stateMachineArn 现在看起来像这样,"arn:aws:states:us-east-1:$context.accountId:stateMachine:AbcStateMachine" 知道如何动态获取当前区域。
    • 问题:由于使用上面的$data 只是使用请求正文的数据字段,如果我们以类似的方式将name 字段通过它,它如何进入请求, 因为它会在$data?我尝试使用$data.name,但它不起作用。有什么想法吗?
    【解决方案2】:

    对于那些正在寻找一种使用 OpenApi 集成和 CloudFormation 将 ApiGatewayStep Functions 状态机 直接连接的方法的人,这是我如何管理的一个示例让它发挥作用:

    这是我设计的可视化工作流程(CloudFormation 文件中的更多详细信息)作为概念证明:

    template.yaml

    AWSTemplateFormatVersion: 2010-09-09
    Transform: 'AWS::Serverless-2016-10-31'
    Description: POC Lambda Examples - Step Functions
    
    Parameters:
      CorsOrigin:
        Description: Header Access-Control-Allow-Origin
        Default: "'http://localhost:3000'"
        Type: String
      CorsMethods:
        Description: Header Access-Control-Allow-Headers
        Default: "'*'"
        Type: String
      CorsHeaders:
        Description: Header Access-Control-Allow-Headers
        Default: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key'"
        Type: String
      SwaggerS3File:
        Description: 'S3 "swagger.yaml" file location'
        Default: "./swagger.yaml"
        Type: String
    
    Resources:
      LambdaRoleForRuleExecution:
        Type: AWS::IAM::Role
        Properties:
          RoleName: !Sub ${AWS::StackName}-lambda-role
          AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action: 'sts:AssumeRole'
                Principal:
                  Service: lambda.amazonaws.com
          Policies:
            - PolicyName: WriteCloudWatchLogs
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action:
                      - 'logs:CreateLogGroup'
                      - 'logs:CreateLogStream'
                      - 'logs:PutLogEvents'
                    Resource: 'arn:aws:logs:*:*:*'
    
      ApiGatewayStepFunctionsRole:
        Type: AWS::IAM::Role
        Properties:
          Path: !Join ["", ["/", !Ref "AWS::StackName", "/"]]
          AssumeRolePolicyDocument:
            Version: 2012-10-17
            Statement:
              - Sid: AllowApiGatewayServiceToAssumeRole
                Effect: Allow
                Action:
                  - 'sts:AssumeRole'
                Principal:
                  Service:
                    - apigateway.amazonaws.com
          Policies:
            - PolicyName: CallStepFunctions
              PolicyDocument:
                Version: 2012-10-17
                Statement:
                  - Effect: Allow
                    Action:
                      - 'states:StartExecution'
                    Resource:
                      - !Ref Workflow
    
      Start:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: !Sub ${AWS::StackName}-start
          Code: ../dist/src/step-functions
          Handler: step-functions.start
          Role: !GetAtt LambdaRoleForRuleExecution.Arn
          Runtime: nodejs8.10
          Timeout: 1
    
      Wait3000:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: !Sub ${AWS::StackName}-wait3000
          Code: ../dist/src/step-functions
          Handler: step-functions.wait3000
          Role: !GetAtt LambdaRoleForRuleExecution.Arn
          Runtime: nodejs8.10
          Timeout: 4
    
      Wait500:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: !Sub ${AWS::StackName}-wait500
          Code: ../dist/src/step-functions
          Handler: step-functions.wait500
          Role: !GetAtt LambdaRoleForRuleExecution.Arn
          Runtime: nodejs8.10
          Timeout: 2
    
      End:
        Type: AWS::Lambda::Function
        Properties:
          FunctionName: !Sub ${AWS::StackName}-end
          Code: ../dist/src/step-functions
          Handler: step-functions.end
          Role: !GetAtt LambdaRoleForRuleExecution.Arn
          Runtime: nodejs8.10
          Timeout: 1
    
      StateExecutionRole:
        Type: AWS::IAM::Role
        Properties:
          AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Principal:
                  Service:
                    - !Sub states.${AWS::Region}.amazonaws.com
                Action:
                  - 'sts:AssumeRole'
          Policies:
            - PolicyName: "StatesExecutionPolicy"
              PolicyDocument:
                Version: "2012-10-17"
                Statement:
                  - Effect: "Allow"
                    Action: "lambda:InvokeFunction"
                    Resource:
                      - !GetAtt Start.Arn
                      - !GetAtt Wait3000.Arn
                      - !GetAtt Wait500.Arn
                      - !GetAtt End.Arn
    
      Workflow:
        Type: AWS::StepFunctions::StateMachine
        Properties:
          StateMachineName: !Sub ${AWS::StackName}-state-machine
          RoleArn: !GetAtt StateExecutionRole.Arn
          DefinitionString: !Sub |
            {
              "Comment": "AWS Step Functions Example",
              "StartAt": "Start",
              "Version": "1.0",
              "States": {
                "Start": {
                  "Type": "Task",
                  "Resource": "${Start.Arn}",
                  "Next": "Parallel State"
                },
                "Parallel State": {
                  "Type": "Parallel",
                  "Next": "End",
                  "Branches": [
                    {
                      "StartAt": "Wait3000",
                      "States": {
                        "Wait3000": {
                          "Type": "Task",
                          "Resource": "${Wait3000.Arn}",
                          "End": true
                        }
                      }
                    },
                    {
                      "StartAt": "Wait500",
                      "States": {
                        "Wait500": {
                          "Type": "Task",
                          "Resource": "${Wait500.Arn}",
                          "End": true
                        }
                      }
                    }
                  ]
                },
                "End": {
                  "Type": "Task",
                  "Resource": "${End.Arn}",
                  "End": true
                }
              }
            }
    
      RestApi:
        Type: AWS::Serverless::Api
        Properties:
          StageName: !Ref Environment
          Name: !Sub ${AWS::StackName}-api
          DefinitionBody:
            'Fn::Transform':
              Name: AWS::Include
              Parameters:
                # s3 location of the swagger file
                Location: !Ref SwaggerS3File
    

    swagger.yaml

    openapi: 3.0.0
    info:
      version: '1.0'
      title: "pit-jv-lambda-examples"
      description: POC API
      license:
        name: MIT
    
    x-amazon-apigateway-request-validators:
      Validate body:
        validateRequestParameters: false
        validateRequestBody: true
      params:
        validateRequestParameters: true
        validateRequestBody: false
      Validate body, query string parameters, and headers:
        validateRequestParameters: true
        validateRequestBody: true
    
    paths:
      /execute:
        options:
          x-amazon-apigateway-integration:
            type: mock
            requestTemplates:
              application/json: |
                {
                  "statusCode" : 200
                }
            responses:
              "default":
                statusCode: "200"
                responseParameters:
                  method.response.header.Access-Control-Allow-Headers:
                    Fn::Sub: ${CorsHeaders}
                  method.response.header.Access-Control-Allow-Methods:
                    Fn::Sub: ${CorsMethods}
                  method.response.header.Access-Control-Allow-Origin:
                    Fn::Sub: ${CorsOrigin}
                responseTemplates:
                  application/json: |
                    {}
          responses:
            200:
              $ref: '#/components/responses/200Cors'
        post:
          x-amazon-apigateway-integration:
            credentials:
              Fn::GetAtt: [ ApiGatewayStepFunctionsRole, Arn ]
            uri:
              Fn::Sub: arn:aws:apigateway:${AWS::Region}:states:action/StartExecution
            httpMethod: POST
            type: aws
            responses:
              default:
                statusCode: 200
                responseParameters:
                  method.response.header.Access-Control-Allow-Headers:
                    Fn::Sub: ${CorsHeaders}
                  method.response.header.Access-Control-Allow-Origin:
                    Fn::Sub: ${CorsOrigin}
              ".*CREATION_FAILED.*":
                statusCode: 403
                responseParameters:
                  method.response.header.Access-Control-Allow-Headers:
                    Fn::Sub: ${CorsHeaders}
                  method.response.header.Access-Control-Allow-Origin:
                    Fn::Sub: ${CorsOrigin}
                responseTemplates:
                  application/json: $input.path('$.errorMessage')
            requestTemplates:
              application/json:
                Fn::Sub: |-
                  {
                    "input": "$util.escapeJavaScript($input.json('$'))",
                    "name": "$context.requestId",
                    "stateMachineArn": "${Workflow}"
                  }
          summary: Start workflow
          responses:
            200:
              $ref: '#/components/responses/200Empty'
            403:
              $ref: '#/components/responses/Error'
    
    components:
      schemas:
        Error:
          title: Error
          type: object
          properties:
            code:
              type: string
            message:
              type: string
    
      responses:
        200Empty:
          description: Default OK response
    
        200Cors:
          description: Default response for CORS method
          headers:
            Access-Control-Allow-Headers:
              schema:
                type: "string"
            Access-Control-Allow-Methods:
              schema:
                type: "string"
            Access-Control-Allow-Origin:
              schema:
                type: "string"
    
        Error:
          description: Error Response
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
          headers:
            Access-Control-Allow-Headers:
              schema:
                type: "string"
            Access-Control-Allow-Origin:
              schema:
                type: "string" 
    

    step-functions.js

    exports.start = (event, context, callback) => {
        console.log('start event', event);
        console.log('start context', context);
        callback(undefined, { function: 'start' });
    };
    exports.wait3000 = (event, context, callback) => {
        console.log('wait3000 event', event);
        console.log('wait3000 context', context);
        setTimeout(() => {
            callback(undefined, { function: 'wait3000' });
        }, 3000);
    };
    exports.wait500 = (event, context, callback) => {
        console.log('wait500 event', event);
        console.log('wait500 context', context);
        setTimeout(() => {
            callback(undefined, { function: 'wait500' });
        }, 500);
    };
    exports.end = (event, context, callback) => {
        console.log('end event', event);
        console.log('end context', context);
        callback(undefined, { function: 'end' });
    };
    

    【讨论】:

    • 这是一场噩梦。
    • @Nihil,它的哪一部分?也许我的示例包含 IaC 以及 Api Gateway 和 Step Functions 之间的直接连接会带来更多复杂性并且还不够简单?
    • 不是你的例子 Julio,这实际上很有帮助。我的意思是使用 API Gateway 触发 Step Functions 的整个设置。需要很多资源(方法、集成、响应、响应方法……),奇怪的请求 ARN/URI(apigateway:{region}:action/StartExecution),请求模板语法……都非常复杂。得到它的工作(在你的帮助下,谢谢!),现在它看起来很简单,但是当开始时 - 也可能是粒子物理学(你有一个上夸克,下夸克,奇怪和美丽 - 还有什么更简单的?)
    • 是的,是的,一开始看起来很复杂,现在我已经习惯了,并且在两者之间不使用 lambda 会带来架构上的满足感,因为也许那个解决方案(使用 lambdas)会更简单的例子。很高兴知道它对您有所帮助。
    • @m52509791,总是存在考虑使用 Lambda 等待 Step Function 实例结束的不太优雅的解决方案,但如果该过程需要更多时间,您将支付等待时间和错误超过 30 秒(Api Gateway 的请求有 30 秒超时)。更好的选择是通过 Api Gateway 使用 websockets,让客户端知道进程已经结束。
    猜你喜欢
    • 2021-05-16
    • 1970-01-01
    • 2019-08-11
    • 1970-01-01
    • 2017-05-02
    • 1970-01-01
    • 2021-10-29
    • 1970-01-01
    • 2017-05-29
    相关资源
    最近更新 更多