AWS serverless 和 API应用程序

158 阅读8分钟

正文

  • 什么是serverless?

          官方介绍中与serverless相对应的词是traditional。
          traditional对应的是传统的开发模式。构建一套应用程序的过程中不仅需要写好有关商业的逻辑,
      还要考虑一些额外的内容,比如服务器的配置,系统的升级等;这样使得开发人员在开发的过程中花费了
      大量的精力放在了与商业逻辑无关的工作上。
          相对而言serverless就是将开发人员解放出来,更加集中与商业逻辑相关代码的编写,而并不需要
      在意运维以及一些其他的操作,AWS提供的相关的服务,开箱即用。
          此外需要引入lambda的概念,这是撰写商业逻辑相关代码的工具。       
    
  • 创建第一个API

      1、在AWS服务中选择API Gateway
      2、创建API
      3、选择需要创建API的类型,在生产环境上建议创建rest api,原因在于rest api更加强大也更加规范
      此外,WebSocket API 更加适合需要实时连接的场景,游戏公司一般会使用此种。当然在付费的角度上看,
      rest api调用也是价格最高的
      4、创建好API之后需要给API创建,方便API对不同资源的访问得到不同的响应
      5、对API创建的资源创建方法,在此我们创建一个GET方法,选择模拟,意思是当外部调用我们创建的这个API
      时,并不调用AWS的服务,而是我们可以自定义返回的相关内容
      6、在集成响应中配置映射模板,添加映射模板,添加数据
          {
              "name":"hello world"
          }
      7、部署API 填写相关内容
      8、访问API对外提供的URL。注意,实际上需要访问的时我们配置的资源,所以实际的URL还要再加上资源地址
      https://6u8dpai033.execute-api.us-west-1.amazonaws.com/dev/fiirst-api-test2
      返回
          {
          "name":"hello world"
          }
      这样,我们就创建好了第一个API
      
    
  • 对于不同的需求需要用到的服务

image.png

  • API获取数据的流程

image.png

    当WEB API接收到请求后,可以触发lambda,在这里可以写代码逻辑,当然也可以做权限控制

下面介绍一下创建API方法后,不同流程的作用

image.png

    上图模仿了API请求的流程
    方法请求:控制方法请求的格式,设置权限
    集成请求:当收到请求时,可以和请求集成的操作,比如调用lambda
    集成响应:可以设置响应的结果
    方法响应:设置最终响应的格式,并且返回客户端
    

  • 上面我们建立了一个简单的API,不涉及数据的传输,接下来,设计一个更加强大的API,并且与lambda进行交互

      1、首先创建一个lambda函数,可以选择空白或者蓝图,蓝图就是会有标准的功能模板
      2、选择需要编写的语言,在这里选择了python
      3、创建执行的角色,因为lambda可能需要调用其他的aws service所以先需要设置好权限,也可以选择创建
      具有权限的角色,系统会自动创建一个角色供lambda使用
      4、这样我们就成功创建了一个lambda函数,具体的界面还有许多其他的设置,可自行探索
      
      5、以下是系统创建的lambda函数,我们来看一下含义
    
      import json
    
      def lambda_handler(event, context):
          # TODO implement
          return {
              'statusCode': 200,
              'body': json.dumps('Hello from Lambda!')
          }
     
     定义了一个方法lambda_handler,这个方法是系统自己定义的,可以更改名字,但是更改名字的同时需要注意
     同时需要更改运行时设置的处理程序,须一致。
     其次方法有两个参数,event是一个外界传入的参数,是json类型,此方法中未用到event,另外还有context
     笔者猜测,context是配置系统设置的一个参数。应该是系统运行时自动传入的一个参数
     此外,方法体内部返回一个json格式的数据包含了相应码和具体的数据
     
     6、当lambda函数创建好了之后,可以进行测试 选择 configure test event ,将event进行实例化
     以下就是系统生成的event模板,可以看出来,是一个json格式的数据
         {
            "key1": "value1",
            "key2": "value2",
            "key3": "value3"
          }
    7、配置好event之后可以点击测试
        Response
          {
            "statusCode": 200,
            "body": "\"Hello from Lambda!\""
          }
      可以看出来,测试的结果成功返回了lambda方法体定义的结果
     
    
  • 在此,我们就创建了一个简单的lambda函数,接下来看看如何与API进行交互

      1、同样的我们需要创建一个新的API
      2、我们需要创建两个资源点
          get方法,对应的操作是获取数据
          /fetch-data 
          post方法,对应的操作是将数据传到服务器 
          /store-data
      3、需要对API进行流程的设置
          对于/fetch-data ,在集成请求中选择模拟,系统不会进行操作,所以我们自己需要配一下返回的响应体
        在集成响应中配上方法体  
          {
          name:"zhangsan"
          age:"22"
      }
          对于/store-data,在集成请求中选择lambda函数,选择函数所在的区域以及名字
      4、选择操作-启用CORS-启用CORS并替换现有的CORS标头(都需要此操作)
          原因在于此操作可以标准化相应头的格式,笔者目前尚未深究
      5、部署此API,并选择继承方法
      
    
  • 以上我们配好了调用lambda的API ,接下来我们使用一个网站来call这个API观察是否有结果

    A Pen by yuerushui (codepen.io)

    var xhr = new XMLHttpRequest();
    xhr.open('GET','https://6ey90rummb.execute-api.us-west-1.amazonaws.com/dev/fetch-data')
    xhr.onreadystatechange = function(event){
      console.log(event.target.response);
    }
    xhr.send();
    -----
    var xhr = new XMLHttpRequest();
    xhr.open('POST','https://6ey90rummb.execute-api.us-west-1.amazonaws.com/dev/store-data')
    xhr.onreadystatechange = function(event){
      console.log(event.target.response);
    }
    xhr.send();
    

    可以看出右侧返回了我们设置的结果

image.png

上面讨论了如何创建API和lambda进行联动,下面介绍一些更深的应用方法

  • 模型 在引入模型概念之前,提出一个问题,如果通过API传入的数据结构过于复杂,但是在lambda中只需要其中的一部分数据,可是我们需要在lambda中写大量的方法来处理这些与商业逻辑无关的数据,不够优美,所以接下来可以使用将数据处理与逻辑计算分开,在分开的处理步骤中就使用了模型

回顾一下API调用的流程

image.png

  • 方法请求

image.png

    方法请求是API调用经过的第一关,在这里,我们可以验证这个API的请求是否符合规范,上图 请求验证程序选择了验证正文
同样的,这里可以选择验证请求头,那我们如何验证呢?验证标准是什么?
    请看下面的内容类型,增加一条 内容类型是 "application/json" 表示传入的请求正文是json的格式,并且需要符合内容标准
    以下为验证的json,可以直接设置为模板,然后在此处验证时直接使用这个模板即可
{
    "$schema":"http://json-schema.org/draft-04/schema#",
    "title":"CompareData",
    "type":"object",
    "properties":{
        "age":{"type":"integer"},
        "height":{"type":"integer"},
        "income":{"type":"integer"}
    },
    "required":["age","height","income"]
}

上述模板中比较重要的部分是 properties 和 required 表示需要传入三个属性并且一个不能少,否则调用API会报方法体不正确的
错误
  • 集成请求

image.png

image.png

    集成请求是API调用的第二步,当API调用来到了集成请求,说明第一关校验已经通过了,也拿到了API的请求数据,
接下来要做就是设置下一步要进行的操作,在此我们选择lambda函数表示调用lambda,那之前我们提过,如果将复杂的数据
处理从lambda中剥离出来,那么这一步就是可以完成这个需求,同样的我们需要选择映射模板-当未定义模板-设置
Content-Type是application/json表示正文是json格式,然后可以应用之前创建的模板,如下

#set($inputRoot = $input.path('$'))
    {
      "age" : 42,
      "height" : 42,
      "income" : 42
    }
 -----  
$input.path('$') 表示解析传入的json数据,得到json的请求体
 {
      "age" : 42,
      "height" : 42,
      "income" : 42
    }
则表示返回的数据类型,在这里如果这样写将给lambda返回固定死的数据,所以我们需要改一下
{
      "age2" : $inputRoot.age,
      "height2" : $inputRoot.height,
      "income2" : $inputRoot.income
    }
这样就把数据格式转换了一下,传给lambda
  • lambda调用

      import json
      import logging
    
      def lambda_handler(event, context):
          # logging.info(event)
          # TODO implement
          age = event['age2']
          return age*2
      
      以上就是lambda的主题代码了,核心就是去接受event里面的age2属性,并且乘以2进行返回
      
      那我们在集成请求中其实定义了传给lambda的age2就是 "age2" : $inputRoot.age 。其实就是从input
      正文中取到的age
    
  • 集成相应

image.png

    我们就到了集成相应这一步,我们拿到了lambda返回的数据是 age*2 我们是否可以包装这个结果,答案是可以,就在这一
步进行。同样的我们在映射模板中创建一个新的模板设置 Content-Type"application/json",文本中可以自己定义json
的格式,也可以使用之前创建好的模板,如果使用了模板,那么会自动生成文本,也可以进行修改
  • 方法响应

      方法响应决定了最终返回内容的形状
    
  • 接下来我们测试一下

image.png

    可以看到当我们请求正文是
    {
    "age":22,
    "height":123,
    "income":2000
    }
    
    返回的结果是

    {
      "your-age": 44,
      "height": null,
      "income": null
    }
和我们预期是一致的
  • 加餐:CloudWatch

如果想查看具体日志可以打开CloudWatch,在日志-日志组,找到相应的日志。

  • 接下来我们考虑一个需求,如果想要生成两个get资源,分别对应获取一条数据和全部数据,应该如何操作呢?

    按照之前的步骤,应该是创建两个资源,在创建两个lambda函数分别对应获取单个数据和全部数据,但是有没有更好的方法,答案是有的。

image.png

如上图,我们在创建资源的时候,并未锁定资源是all还是single,我们用type来表示资源。那后续如果可以动态识别API访问的路径,然后传给lambda,然后在lambda中进行逻辑判断返回不同的值即可,具体做法如下。

    1、 编写lambda 代码块
    import json

    def lambda_handler(event, context):
        # TODO implement
        if event["type"] = 'all':
            return "get all"
        else if event["type"] = 'single':
             return "get single"
        return {
            'statusCode': 200,
            'body': json.dumps('Hello from Lambda!')
        }
        
    2、那如何从请求的url中获取type的值且当作请求体传给lambda呢?get方法本来是没有请求体的,所以我们需要
    在集成请求中将数据进行转换,很自然,应为集成请求就负责将数据传给lambda,下面是集成请求中的方法
            {
            "type" : "$input.params('type')"
            }
    可以看出来,通过$input.params('type')获取url中的资源地址发送给lambda
    
    
    下面提供了官方文档,可以进行查阅

API Gateway mapping template and access logging variable reference - Amazon API Gateway

  • codepen 进行url测试 image.png 可以看出来,当对不同的url进行call的时候,返回了不同的结果

这期就讲到这里,谢谢观看