如何通过API Gateway调用AWS Step Function

12
我如何通过API Gateway的POST请求以及请求的JSON负载来调用AWS Step Function?
2个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
27

1. 创建您的步骤函数

显而易见。我想,如果您正在阅读此内容,则应该知道如何操作。

否则,您可以前往此处的文档查看:什么是AWS Step Functions?.


2. 为您的API创建IAM角色

它可以用于所有步骤函数,也可以只用于此步骤函数。我们只会涵盖第一种情况,如Amazon教程中所述:使用API Gateway创建API.

  

创建IAM角色

     
      
  • 登录到AWS身份和访问管理控制台。

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

  •   
  • 在设置角色名称页面上,输入Role Name为APIGatewayToStepFunctions,然后选择下一步。

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

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

  •   
  • 在审核页面上,注意Role ARN,例如:

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

  •   
  • 选择创建角色。
  •   
     

将策略附加到IAM角色

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

3. 设置

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

如Ka Hou Ieong在如何通过API Gateway调用AWS Step Functions?中所述,您可以通过API Gateway控制台创建AWS Service集成,如下所示:

  • 集成类型:AWS服务
  • AWS服务:步骤函数
  • HTTP方法:POST
  • 操作类型:使用操作名称
  • 操作:StartExecution
  • 执行角色:启动执行的角色 (刚刚创建的角色。只需粘贴其ARN)
  • 标头:

    X-Amz-Target -> 'AWSStepFunctions.StartExecution'
    Content-Type -> 'application/x-amz-json-1.0'

  • Body Mapping Templates/Request payload:

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

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

一切都与2.a相同,除了请求体映射模板。您需要将其转换为字符串。例如使用$util.escapeJavascript()。它将整个请求体作为输入传递给您的步骤函数。

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

注意事项

  • stateMachineArn: 如果您不想将 stateMachineArn 作为请求 API Gateway 的一部分传递,可以在您的 Body Mapping 模板内将其硬编码(参见 AWS API Gateway with Step Function
  • name: 省略 name 属性将导致 API Gateway 在每次执行时为您生成不同的名称。

这是我的第一个“自问自答”,所以可能这不是正确的方式,但我花了相当多的时间来尝试理解我的 Mapping Template 出了什么问题。希望这能帮助节省其他人的时间和精力。


1
请注意,StepFunction 执行的最大有效载荷大小为 32K。因此,如果您的请求体超过该大小,建议 API 调用 Lambda 将请求体存储在 S3 中,然后执行 StepFunction,并传递 S3 存储桶/键。这样可以使 API 逻辑更简单,并且对于可变数据大小更加稳健。 - Geoff
完全正确。而且这个限制也在StepFunction中得到了执行(当步骤需要从源下载图片时),并以同样的方式解决。我应该更新我的答案吗? - ElFitz
@ElFitz 参考了你的帖子后,对我帮助很大。在第3.b节中,我想要动态填充region和account id来形成stateMachineArn。因为在我的情况下,Stepfunction和API Gateway位于同一区域和账户ID中。我能够从$context.accountId中获取账户ID,但是找不到任何可以提供当前区域名称的内容。在我的模板中,stateMachineArn现在看起来像这样,"arn:aws:states:us-east-1:$context.accountId:stateMachine:AbcStateMachine"有什么想法可以让我动态获取当前区域吗? - rockyPeoplesChamp
1
由于上述使用$data只是使用请求正文的数据字段,如果我们以类似的方式传递name字段,它将如何进入请求?因为它将在$data内部。我尝试使用$data.name,但它没有起作用。有什么想法吗? - ChumiestBucket

12

对于那些想要使用OpenApi集成和CloudFormation直接连接 ApiGatewayStep Functions State Machine的人,这是我成功实现它的示例:

这是我设计的可视化工作流程(有关详细信息,请参见CloudFormation文件)作为概念验证:

visual workflow

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' });
};

3
这真是噩梦成真。 - Nihil
@Nihil,是哪一部分内容?也许我的示例包括IaC和Api Gateway与Step Functions之间的直接连接会更加复杂,不够简单明了? - Julio Villane
1
不是你的例子,那真的很有帮助。我指的是使用API Gateway触发Step Functions的整个设置。需要许多资源(方法、集成、响应、响应方法...),奇怪的请求ARN/URI(apigateway:{region}:action/StartExecution),请求模板语法...都非常复杂。 在你的帮助下,我已经搞定了(谢谢!),现在看起来足够简单,但刚开始时可能就像粒子物理学一样复杂(你有上夸克、下夸克、奇异和美丽 - 还有什么比这更简单的呢?) - Nihil
3
是的,这是真的,一开始看起来很复杂,但现在我已经习惯了。不使用 lambda 函数会有一种架构上的满足感,因为也许使用 lambda 函数会使示例更简单。很高兴知道它对你有帮助。 - Julio Villane
1
@m52509791,总有一种不太优雅的解决方案,可以考虑使用Lambda等待Step Function实例的结束,但这会花费等待时间,并在处理过程超过30秒时出现错误(Api Gateway的请求有30秒的超时时间) 。更好的选择是通过Api Gateway使用Websockets,让客户端知道过程已结束。 - Julio Villane
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,