如何使用AWS Chalice创建CRUD REST API

如何使用AWS Chalice创建CRUD REST API

Python Weekly推送的一篇AWS Chalice入门教程,翻译了一下。

本文翻译自: How to Create CRUD REST API with AWS Chalice

aws

摘要:通过本文,你将学习怎样使用AWS Chalice构建无服务器的Python应用。你将构建一个图书数据库REST API来存储图书条目,并在该API上执行CRUD操作。教程的代码在此处提供 AWS Chalice API sample

引言

AWS Chalice是亚马逊开发的无服务器Python框架。它与Flask在语法和语义上有许多的相似之处。像其他无服务器技术一样,Chalice让开发者能够专注于应用程序开发,而无需管理服务器。它使用 AWS LambdaAmazon API 网关

你将使用 Chalice 构建一个图书数据库REST API。 你的用户能够存储图书条目并使用API执行CRUD操作。 条目会存储在 DynamoDB 数据库中,这是Amazon提供的NoSQL数据库。以表,项和属性的形式存储数据。

最后,你还能了解如何将Auth0身份验证集成到AWS Chalice应用程序中。 你将使用Auth0进行授权。 权限管理让你区分普通公众可以访问的端点和应用认证用户可以访问的端点。

先决条件

  1. Python 3.7 或更新版本
  2. pip
  3. venv
  4. AWS 账号
  5. AWS CLI

确认你的机器中已经有Python并安装了 pip。你可以从 Python官网 下载对应系统版本的Python。 可以按说明安装 pip

Venv是用于为机器上的单个项目创建隔离环境的包。 如果安装了Python 3.7或更高版本,Venv已经预装在Python包中了。

你需要一个 AWS 账号。 你可以注册一个 。如果你没有在机器上安装AWS CLI 请查看 AWS CLI v2 user guide

1. 配置AWS凭据

要使用Chalice构建应用,你需要在AWS CLIAWS CLI上设置AWS凭据。这将允许你使用Amazon API Gateway和AWS Lambda。

如果你已成功安装了AWS CLI,则可以使用以下命令配置AWS凭据:

aws configure

你会被提示输入AWS Access Key ID,AWS Secret Access Key,Default region name。输入你的信息。Default output format可以使用默认跳过:

AWS Access Key ID [None]: ****************ABCD
AWS Secret Access Key [None]: ****************abCd
Default region name [None]: us-west-2
Default output format [None]:

如果你还没有AWS凭据,则需要在IAM控制台中设置IAM用户。 访问Creating an IAM user in your AWS account 按说明操作。 创建IAM用户后,你可以通过指令 设置AWS Access keys

要检查是否正确设置凭据,请运行以下命令

aws ec2 describe-regions

你应该看到可以使用EC2的所有区域的列表

2. 设置项目依赖

开始时,你需要给你的项目一个虚拟环境。 首先,你需要创建一个新目录,例如chalice-sample。 进入,然后创建虚拟环境。 最后但并非最不重要,不要忘记激活才能使用它。

mkdir chalice-sample
cd chalice-sample
python -m venv env
source env/bin/activate

现在,安装用于Python 的 AWS SDK Boto3。 它让我们的项目可以执行所需的DynamoDB数据库操作。

pip install boto3

然后,安装AWS Chalice:

pip install chalice

接下来,使用以下命令创建一个新的AWS Chalice项目:

chalice new-project chalice-api-sample

上面的命令,指定Chalice用 new-project 命令创建了一个新项目,命名为Chalice-API-Sample

如果你查看Chalice命令生成的新项目结构,它将如下所示:

chalice-api-sample
├── app.py
├── .chalice
│   └── config.json
├── .gitignore
└── requirements.txt

让我们看看每个生成的文件有什么作用:

  • app.py: 这是AWS Lambda的应用程序逻辑存储的位置。
  • .chalice:此文件夹由应用程序配置和数据库设置组成.
  • .gitignore: 如果你正在使用git跟踪项目,则不会被推入远程存储库的文件列表
  • requirements.txt: 应用程序使用的依赖项列表

3. 数据库配置和部署设置

你必须为项目定义必要的部署和数据库配置。

请编辑位于.chalice目录中的config.json文件。config.json文件保存了部署配置。

{
  "version": "2.0",
  "app_name": "chalice-api-sample",
  "stages": {
    "dev": {
      "api_gateway_stage": "api",
      "autogen_policy": false
    }
  }
}

如上图所示,你已经有了一个 stage-namedev的部署阶段。 默认情况下,是Chalice为你取了阶段名称。但是,你也可以使用你喜欢的名称。 请注意,AWS中的阶段是指部署。

  • api_gateway_stage API的URL前缀。
  • autogen_policy 指定Chalice是否应根据应用程序代码自动设置IAM策略。 如果它设置为false,它将检查你定义的IAM策略的policy-<stage-name>.json 文件。

接下来,在.Chalice目录中创建一个名为policy-dev.json的新文件。 它将保存从DynamoDB数据库读取和写入的策略。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogGroup",
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*",
      "Effect": "Allow"
    },
    {
      "Action": [
        "dynamodb:PutItem",
        "dynamodb:DeleteItem",
        "dynamodb:UpdateItem",
        "dynamodb:GetItem",
        "dynamodb:Scan",
        "dynamodb:Query"
      ],
      "Resource": ["arn:aws:dynamodb:*:*:table/my-demo-table"],
      "Effect": "Allow"
    }
  ]
}

在上面的文件中,你为用户提供了创建日志组和日志事件的规定。 你还指定了 DynamoDB 操作,用于在数据库项目上阅读、创建、更新、扫描和查询操作。 最后,你将DynamoDB表命名为my-demo-table

你将使用AWS CloudFormation创建和设置DynamoDB数据库。 CloudFormation是一种用于为AWS项目指定并设置资源和依赖项的工具。 CloudFormation模板,以JSON或YAML格式包含构成堆栈的资源。 CloudFormation使用该模板设置并配置其中指定的资源。

因此,在.Chalice目录中创建名为dynamodb_cf_template.yaml的新文件。 该文件将是CloudFormation将用于创建数据库的模板。

AWSTemplateFormatVersion: "2010-09-09"
Resources:
  chaliceDemo:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: my-demo-table
      AttributeDefinitions:
        - AttributeName: "id"
          AttributeType: "S"
        - AttributeName: "author"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "id"
          KeyType: "HASH"
        - AttributeName: "author"
          KeyType: "RANGE"

      ProvisionedThroughput:
        ReadCapacityUnits: "5"
        WriteCapacityUnits: "5"

Outputs:
  TableName:
    Value: !Ref "chaliceDemo"
    Description: Name of the newly created DynamoDB table

上面的代码由要创建的DynamoDB表的键字段定义组成。 在这种情况下,图书的idauthor字段 将存储图书作者的名字。 KeySchema 字段构成表的主键。 你不需要包含任何其他不是 KeySchemaAttributeDefinitions阵列中的键。 有关DynamoDB表创建中的属性定义的更多信息,你可以查看AWS官方文档

然后,在终端上使用以下命令创建你在上面定义的DynamoDB数据库。

aws cloudformation deploy\
 --template-file dynamodb_cf_template.yaml\
 --stack-name "my-stack"

上面的命令允许你使用CloudFormation在AWS中设置名为 my-stack的堆栈,并使用模板文件中定义的资源。

现在,你可以在localhost上运行和测试设置。 在下面运行命令:

chalice local

你应该获得如下所示终端输出:

Found credentials in shared credentials file: ~/.aws/credentials
Serving on http://127.0.0.1:8000

4. Implement CRUD Features

现在你将定义app.py中的CRUD功能。 你将为创建图书条目,获取所有图书的动作创建端点。 你还将创建用于按ID获取特定图书的端点。 此外,从数据库更新和删除图书也将包含到此端点。

导航到项目的App.py文件。 创建必要的导入并生成一个连接到数据库的函数,如下所示:

from chalice import Chalice, Response
import boto3
from boto3.dynamodb.conditions import Key

app = Chalice(app_name='chalice-api-sample')

def get_app_db():
    dynamodb = boto3.resource("dynamodb")
    table = dynamodb.Table('my-demo-table')
    return table

上面的代码由库导入和应用程序名称的定义组成。它包含一个名为get_app_db的函数,它通过AWS SDK boto3定义DynamoDB表。 该函数还定义要在应用程序中使用的表的名称为my-demo-table

创建图书条目

现在,你将在app.py文件中加入一个使用POST方法生成图书条目的端点:

@app.route('/book', methods=['POST'])
def add_book():
    data = app.current_request.json_body
    try:
        get_app_db().put_item(Item={
            'id': data['id'],
            "title": data['title'],
            "author": data['author']
        })
        return {'message': 'ok - CREATED', 'status': 201, "id": data['id'], "title": data['title'], "author": data['author']}
    except Exception as e:
        return {'message': str(e)}

在上面的代码中,你完成了以下操作:

  • 指定了端点的URL路由为 /book 使用POST方法
  • 在 data = app.current_request.json_body这行,你定义了API Content-Type 将是以何种方式接受请求体并以JSON格式生成响应体。
  • 允许使用图书的id、 title、 和 author添加图书条目
  • 包含新添加的图书的属性的返回声明

你也许会使用HTTPie这个命令行工具测试你的API。可以使用pip install httpie来安装它。如果你已经有HTTPie了,请尝试向数据库添加一本书:

http POST 127.0.0.1:8000/book id=123  title="Javascript Know It All" author="Chukwuma Obinna"

你应该会得到像这样的响应:

HTTP/1.1 200 OK
Content-Length: 110
Content-Type: application/json
Date: Fri, 30 Apr 2021 03:36:22 GMT
Server: BaseHTTP/0.6 Python/3.7.7

{
    "author": "Chukwuma Obinna",
    "id": "123",
    "message": "ok - CREATED",
    "status": 201,
    "title": "Javascript Know It All"
}

然后,加入另一个图书条目:

http POST http GET https://vvyngxvyag.execute-api.us-west-2.amazonaws.com/api/book id=456 title="Python for Primary School" author="Iwobi Peter"

响应会是:

{
    "author": "Iwobi Peter",
    "id": "456",
    "message": "ok - CREATED",
    "status": 201,
    "title": "Python for Primary School"
}

获取数据库中所有的图书

要获取数据库中的所有图书,请在添加下面函数以扫描表并获取所有图书。

@app.route('/', methods=['GET'])
def index():
    response = get_app_db().scan()
    data = response.get('Items', None)
    return {'data': data}

上面的代码中,添加了一个GET 操作的根路由。然后在表中使用 scan方法返回表中所有的项目

使用如下命令访问这个端点:

http GET 127.0.0.1:8000/

你应该会得到如下输出:你应该会得到如下输出:

HTTP/1.1 200 OK
Content-Length: 155
Content-Type: application/json
Date: Fri, 30 Apr 2021 03:52:47 GMT
Server: BaseHTTP/0.6 Python/3.7.7

{
    "data": [
        {
            "author": "Iwobi Peter",
            "id": "456",
            "title": "Python for Primary School"
        },
        {
            "author": "Chukwuma Obinna",
            "id": "123",
            "title": "Javascript Know It All"
        }
    ]
}

通过id获取图书

现在,你将加入一个通过特定的id获取特定数据的路由:

@app.route('/book/{id}', methods=['GET'])
def get_book(id):
    response = get_app_db().query(
        KeyConditionExpression=Key("id").eq(id)
    )
    data = response.get('Items', None)
    return {'data': data}

在上面的代码中:

  • /book/{id} 路通过由GET操作接受特定的图书的id
  • get_book 函数接受 id 参数
  • KeyConditionExpression 设置为使用id 参数查询 DynamoDB表
  • 然后返回查询id的表项

使用HTTPie测试,获取一本id为 123的特定图书:

http GET 127.0.0.1:8000/book/123

你应该会得到如下输出:

HTTP/1.1 200 OK
Content-Length: 110
Content-Type: application/json
Date: Fri, 30 Apr 2021 03:36:22 GMT
Server: BaseHTTP/0.6 Python/3.7.7

{
    "author": "Chukwuma Obinna",
    "id": "123",
    "message": "ok - CREATED",
    "status": 201,
    "title": "Javascript Know It All"
}

更新一个图书条目

此时,你将添加一个更新图书标题的端点。 你将使用与添加图书相同的URL。 当然,你也可以使用新的路由URL。 你将使用PUT方法更新REST API中项目的,如下所示:

@app.route('/book/{id}', methods=['PUT'])
def update_book(id):
    data = app.current_request.json_body
    try:
        get_app_db().update_item(Key={
            "id": data['id'],
            "author": data['author']
        },
            UpdateExpression="set title=:r",
            ExpressionAttributeValues={
            ':r': data['title']
        },
            ReturnValues="UPDATED_NEW"
        )
        return {'message': 'ok - UPDATED', 'status': 201}
    except Exception as e:
        return {'message': str(e)}

在上面的代码中,update_book 函数接受id参数。 此外,在创建 DynamoDB 时,你还提供了 Key 元素,其中包含KeySchema中指定的属性。。 然后,你使用UpdateExpressionExpressionAttributeValues来指定将更新的属性。

要查看更新功能是否生效,请将书名更新为 id 123

http PUT 127.0.0.1:8000/book/123 id=123 title="Chalice Book" author="Chukwuma Obinna"

你应该得到这样的输出:

HTTP/1.1 200 OK
Content-Length: 39
Content-Type: application/json
Date: Fri, 30 Apr 2021 12:22:50 GMT
Server: BaseHTTP/0.6 Python/3.7.7

{
    "message": "ok - UPDATED",
    "status": 201
}

现在,查看更新的条目:

http GET 127.0.0.1:8000/book/123

输出结果如下:

{
    "data": [
        {
            "author": "Chukwuma Obinna",
            "id": "123",
            "title": "Chalice Book"
        }
    ]
}

删除图书条目

现在,你将添加删除图书条目的代码。你将添加一个接受id 参数并使用 DELETE 方法的路由。在 app.py文件中添加以下代码:

@app.route('/book/{id}', methods=['DELETE'])
def delete_book(id):
    data = app.current_request.json_body
    try:
        response = get_app_db().delete_item(
            Key={
                "id": data['id'],
                "author": data['author']
            }
        )
        return {'message': 'ok - DELETED', 'status': 201}

    except Exception as e:
        return {'message': str(e)}

上面的delete_book 函数接受 /book/{id}路由中的 id 参数。然后,delete_item 方法应用于 DynamoDB 表上。用Key 属性指定 delete_item方法要在数据库中删除的指定项。

然后,你可以尝试删除数据库中的书条目:

http DELETE 127.0.0.1:8000/book/456 id=456 author="Iwobi Peter"

命令行输出如下:

{
    "message": "ok - DELETED",
    "status": 201
}

5. 部署到 AWS

在将代码部署到 AWS 之前,请确保按照 AWS 用户指南中的定义为你的 IAM 帐户设置了必要的权限。现在,只需要一个命令,即chalice deploy,就可以将你的 Chalice 应用程序部署到 AWS。

chalice deploy

你应该在你的终端上获得 API URL 端点和 Lambda ARN,你可以使用它们与 API 交互。

Creating deployment package.
Creating IAM role: chalice-api-sample-dev-api_handler
Creating lambda function: chalice-api-sample-dev
Creating Rest API
Resources deployed:
  - Lambda ARN: arn:aws:lambda:us-west-2:xxxxxxxxxxxx:function:chalice-api-sample-dev
  - Rest API URL: https://vvyngxvyag.execute-api.us-west-2.amazonaws.com/api/

现在,如果你查看文件夹结构,你会发现一些新的,自动生成的文件,以适应部署的变化:

chalice-api-demo
├── app.py
├── .chalice
|    ├── deployed
|    ├── deployments
│   └── config.json
├── .gitignore
└── requirements.txt

6. 测试已部署的 Chalice API

现在,你可以测试由 Chalice 生成的 REST API URL:

http GET https://vvyngxvyag.execute-api.us-west-2.amazonaws.com/api/

输出结果如下:

HTTP/1.1 200 OK
Connection: keep-alive
Content-Length: 73
Content-Type: application/json
Date: Sun, 02 May 2021 00:32:35 GMT
Via: 1.1 xxxxxxxxxx.cloudfront.net (CloudFront)
X-Amz-Cf-Id: xxxxxxxxxxxxxxxx==
X-Amz-Cf-Pop: AMS1-C1
X-Amzn-Trace-Id: Root=1-xxxxxxxxx-xxxxxxxxxxxxxx;Sampled=0
X-Cache: Miss from cloudfront
x-amz-apigw-id: xxxxxxxxxxxxxx=
x-amzn-RequestId: xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxxx

{
    "data": [
        {
            "author": "Chukwuma Obinna",
            "id": "123",
            "title": "Chalice Book"
        }
    ]
}

祝贺你!你已经使用 AWS Chalice 构建并测试了 REST API。如果你想删除你的应用程序,你可以使用以下命令:

chalice delete

不要忘记删除你创建的 DynamoDB 表:

aws cloudformation delete-stack --stack-name my-stack

7. 添加认证

现在,你已经使用 REST API 和 DynamoDB 设置了 Chalice 应用程序。你可以考虑向应用程序添加身份验证和授权特性。Auth0允许你对用户进行身份验证。它还使你能够提供对 REST API中的端点的授权访问。要使用 Auth0身份验证,你需要一个 Auth0帐户。如果你还没有账户,你可以在这里注册一个免费的 Auth0账户

注册或登录后,到你的仪表板并找到Accounts标签。选择 Create Application。为你的应用程序提供一个名称并选择 Regular Web Applications。创建应用程序后,跳转到设置选项卡,查看连接圣杯应用程序和 Auth0所需要的信息。另外,将你的Chalice应用的 URL 添加到 Auth0应用程序设置中,用于回调和注销 URL。保存更改。

添加 URL后,转到 Chalice 项目的 app.py 文件,以便将 Chalice 应用程序与 Auth0连接。如下所示进行必要的导入,并添加 Auth0应用程序凭证、域名和用户:然后在 app.py 文件中添加以下代码来处理错误:

from chalice import Chalice, Response # imported Response
import boto3
from boto3.dynamodb.conditions import Key
from functools import wraps # new line

AUTH0_DOMAIN = 'dev-xxxxxxxx.us.auth0.com'
API_AUDIENCE = 'https://chalice-demo/'
ALGORITHMS = ["RS256"]
...

然后在 app.py 文件中添加以下代码来处理错误:

class AuthError(Exception):
    def __init__(self, error, status_code):
        self.error = error
        self.status_code = status_code

def handle_auth_error(ex):
    response = jsonify(ex.error)
    response.status_code = ex.status_code
    return response

接下来,添加以下函数,从向 API 发出的请求中的授权头中获取 JSON Web Token (JWT)访问令牌:

def get_token_auth_header():
    """Obtains the Access Token from the Authorization Header
    """
    request = app.current_request
    auth = request.headers.get("Authorization", None)
    if not auth:
        raise AuthError({"code": "authorization_header_missing",
                         "description":
                         "Authorization header is expected"}, 401)

    parts = auth.split()

    if parts[0].lower() != "bearer":
        raise AuthError({"code": "invalid_header",
                         "description":
                         "Authorization header must start with"
                         " Bearer"}, 401)
    elif len(parts) == 1:
        raise AuthError({"code": "invalid_header",
                         "description": "Token not found"}, 401)
    elif len(parts) > 2:
        raise AuthError({"code": "invalid_header",
                         "description":
                         "Authorization header must be"
                         " Bearer token"}, 401)

    token = parts[1]
    return token

添加以下代码。下面的requires_auth 函数是一个装饰器,它检查上面的 get_token_auth_header 获得的访问令牌是否有效。它使用 Auth0帐户中的 JWKS (JSON Web Key set)验证访问令牌。

def requires_auth(f):
    """Determines if the Access Token is valid
    """
    @wraps(f)
    def decorated(*args, **kwargs):
        token = get_token_auth_header()
        jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
        jwks = json.loads(jsonurl.read())
        unverified_header = jwt.get_unverified_header(token)
        rsa_key = {}
        for key in jwks["keys"]:
            if key["kid"] == unverified_header["kid"]:
                rsa_key = {
                    "kty": key["kty"],
                    "kid": key["kid"],
                    "use": key["use"],
                    "n": key["n"],
                    "e": key["e"]
                }
        if rsa_key:
            try:
                payload = jwt.decode(
                    token,
                    rsa_key,
                    algorithms=ALGORITHMS,
                    audience=API_AUDIENCE,
                    issuer="https://"+AUTH0_DOMAIN+"/"
                )
            except jwt.ExpiredSignatureError:
                raise AuthError({"code": "token_expired",
                                 "description": "token is expired"}, 401)
            except jwt.JWTClaimsError:
                raise AuthError({"code": "invalid_claims",
                                 "description":
                                 "incorrect claims,"
                                 "please check the audience and issuer"}, 401)
            except Exception:
                raise AuthError({"code": "invalid_header",
                                 "description":
                                 "Unable to parse authentication"
                                 " token."}, 401)

            app.current_request.context.update(payload)
            return f(*args, **kwargs)
        raise AuthError({"code": "invalid_header",
                         "description": "Unable to find appropriate key"}, 401)
    return decorated

然后,可以使用装饰器@requires_auth 向特定端点添加授权,如下所示。

@requires_auth
app.route('/book', methods=['POST'])
def add_book():
    data = app.current_request.json_body
    ...

因此,如果用户没有登录,Auth0就没有提供用户验证的访问令牌。他们不能使用那些受装饰器@requires_auth 保护的端点。

总结

在本文中,你了解了 AWS Chalice 无服务器技术,使用 Chalice 为图书数据库应用程序构建了 REST API。将 DynamoDB 和 CRUD 功能用 API 集成在一起。你还了解了如何使用 Auth0为某些端点实现授权。

请在下面的评论区告诉我们你的想法,我们希望你能喜欢这篇文章。


发表评论


暂无评论

Top