全部产品
云市场

Python 函数入口

更新时间:2018-12-08 09:29:07

函数计算目前支持以下 Python 运行环境:

  • Python 2.7 ( runtime = python2.7 )
  • Python 3.6 ( runtime = python3 )

在函数计算服务使用 Python 编程,需要定义一个 Python 函数作为入口。Python 运行环境根据是否支持 HTTP 触发器分为 普通函数入口设置 HTTP 触发器 两种函数入口,为函数设置 HTTP 触发器后的函数入口形式会不同,这是为了方便处理发来的 HTTP request 请求。除了设置 HTTP 触发器的函数外,使用 Python 编程的其他所有函数接口都相同。其中 普通函数入口 又分为 处理函数入口initializer入口

本文对 普通函数入口设置 HTTP 触发器 的函数入口进行详细介绍:

普通函数入口

处理函数入口

一个最简单的处理函数定义如下:

  1. def my_handler(event, context):
  2. return 'hello world'

函数名

  • my_handler 需要与创建函数时的 “handler” 字段相对应:例如创建函数时指定的handler为 main.my_handler,那么函数计算会去加载 main.py 中定义的 my_handler 函数。

event 参数

  • event 参数是用户调用函数时传入的数据,在 Python2.7 中是 str 类型,在 Python3 中是 bytes 类型,是函数的输入参数;
  • 函数不对它的内容进行任何解释,您在函数中可以根据实际情况对 event 进行转换:例如输入数据是一个 JSON string ,您可以把它转换成一个 dict :例如:传入的 event :
    1. {
    2. "key": "value"
    3. }
    函数代码:
    1. # -*- coding: utf-8 -*-
    2. import json
    3. def my_handler(event, context):
    4. evt = json.loads(event)
    5. return evt['key']
    返回结果为 value 。

context 参数

  • context 参数中包含一些函数的运行时信息(例如 request id / 临时 AK 等)。您在代码中可以使用这些信息。其类型是 FCContext
  • 其定义如下:
  1. class Credentials:
  2. def __init__(self, access_key_id, access_key_secret, security_token):
  3. self.access_key_id = access_key_id
  4. self.access_key_secret = access_key_secret
  5. self.security_token = security_token
  6. class ServiceMeta:
  7. def __init__(self, service_name, log_project, log_store, qualifier, version_id):
  8. self.name = service_name
  9. self.log_project = log_project
  10. self.log_store = log_store
  11. self.qualifier = qualifier
  12. self.version_id = version_id
  13. class FunctionMeta:
  14. def __init__(self, name, handler, memory, timeout, initializer, initialization_timeout):
  15. self.name = name
  16. self.handler = handler
  17. self.memory = memory
  18. self.timeout = timeout
  19. self.initializer = initializer
  20. self.initialization_timeout = initialization_timeout
  21. class FCContext:
  22. def __init__(self, account_id, request_id, credentials, function_meta, service_meta, region):
  23. self.request_id = request_id
  24. self.credentials = credentials
  25. self.function = function_meta
  26. self.service = service_meta
  27. self.region = region
  28. self.account_id = account_id

可以看到 context 中包含了 6 个信息:

  • request_id: 本次调用请求的唯一 id,您可以把它记录下来在出现问题的时候方便调查。
  • function: 当前调用的函数的一些基本信息如函数名 / 函数入口 / 函数内存 / 超时时间 / 初始化函数入口 / 初始化超时时间。
  • credentials: 函数计算服务通过扮演您提供的 服务角色 获得的一组临时密钥,其有效时间是 15 分钟。您可以在代码中使用它去访问相应的服务( 例如 OSS ),这就避免了您把自己的 AK 信息写死在函数代码里。
  • service: 当前调用的函数所在的 service 的信息,包括 service 的名字,接入的 SLS 的 logProject 和 logStore 信息,service的版本信息qualifier和version_id,qualifier表示调用函数时指定的service版本或别名,version_id表示实际调用的service版本。
  • region: 当前调用的函数所在区域,如 cn-shanghai。
  • accountId: 当前调用函数用户的阿里云 account id。

下面的代码使用临时密钥,向 OSS 中上传了一个文件:

  1. import json
  2. import oss2
  3. def my_handler(event, context):
  4. evt = json.loads(event)
  5. creds = context.credentials
  6. # do not forget security_token
  7. auth = oss2.StsAuth(creds.access_key_id, creds.access_key_secret, creds.security_token)
  8. bucket = oss2.Bucket(auth, evt['endpoint'], evt['bucket'])
  9. bucket.put_object(evt['objectName'], evt['message'])
  10. return 'success'

注意:使用临时秘钥访问 OSS 时要带临时 token。

initializer 入口

一个最简单的 initializer 定义如下:

  1. def my_initializer(context):
  2. print("hello world")

函数名

  • my_initializer 需要与添加 initializer 时的 “initializer” 字段相对应:例如创建函数时指定的 initializer 入口为 main.my_initializer,那么函数计算在配置 initializer 属性后首先会去加载 main.py 中定义的 my_initializer 函数。

initializer 特点

相比于处理函数入口,Python initializer 入口在定义上有以下几个特点:

  • initializer 输入参数只有 context,且包含的信息和处理函数入口的 context 是保持一致的。
  • context 中 initializerinitialization_timeout 两个信息是为 initializer 设计的,当使用 initializer 功能时,会被设置为用户创建函数时所设置的值,否则为空,且不生效。
  • 无返回值,在函数末尾增加 return 操作是无效的。

设置 HTTP 触发器的函数入口

注意:设置了 HTTP 触发器的函数入口与普通函数的入口不同

Python runtime 中函数的签名遵循 Python Web Server Gateway Interface ( 后面统一用简称WSGI称呼 )规范。您可以使用 WSGI 规范对请求进行处理。

HTTP 触发器提供两种函数入口:

  1. # Method 1: User provide the function. FC call the function to process request and send back response.
  2. HELLO_WORLD = b"Hello world!\n"
  3. def handler(environ, start_response):
  4. context = environ['fc.context']
  5. request_uri = environ['fc.request_uri']
  6. for k, v in environ.items():
  7. if k.startswith("HTTP_"):
  8. # process custom request headers
  9. pass
  10. # get request_body
  11. try:
  12. request_body_size = int(environ.get('CONTENT_LENGTH', 0))
  13. except (ValueError):
  14. request_body_size = 0
  15. request_body = environ['wsgi.input'].read(request_body_size)
  16. # get request_method
  17. request_method = environ['REQUEST_METHOD']
  18. # get path info
  19. path_info = environ['PATH_INFO']
  20. # get server_protocol
  21. server_protocol = environ['SERVER_PROTOCOL']
  22. # get content_type
  23. try:
  24. content_type = environ['CONTENT_TYPE']
  25. except (KeyError):
  26. content_type = " "
  27. # get query_string
  28. try:
  29. query_string = environ['QUERY_STRING']
  30. except (KeyError):
  31. query_string = " "
  32. print 'request_body: {}'.format(request_body)
  33. print 'method: {}\n path: {}\n query_string: {}\n server_protocol: {}\n'.format(request_method, path_info, query_string, server_protocol)
  34. # do something here
  35. status = '200 OK'
  36. response_headers = [('Content-type', 'text/plain')]
  37. start_response(status, response_headers)
  38. # return value must be iterable
  39. return [HELLO_WORLD]
  1. # Method 2: User provide the callable class object. FC call the object to process request and send back response.
  2. HELLO_WORLD = b"Hello world!\n"
  3. class AppClass:
  4. """Produce the same output, but using a class
  5. """
  6. def __init__(self, environ, start_response):
  7. self.environ = environ
  8. self.start = start_response
  9. def __iter__(self):
  10. status = '200 OK'
  11. response_headers = [('Content-type', 'text/plain')]
  12. self.start(status, response_headers)
  13. yield HELLO_WORLD
  14. def handler(environ, start_response):
  15. return AppClass(environ, start_response)

函数入口参数

  • environ : environ 参数是一个 python 字典,里面存放了所有和客户端相关的信息,具体详情参考 environ 参数,函数计算增加了两个自定义的 key,分别是 fc.contextfc.request_uri

    • fc.context : 和普通函数入口的 context 意义一样;
    • fc.request_uri : string 类型,request 的 url。

    需要注意的点:environ 中的 HTTP_Variables ,里面包含 request 中 header, 比如某个请求的 header 的为 'x-Custom-key':'value' , 在 environ 中会表现为:environ['HTTP_X_CUSTOM_KEY']='value', 可以理解为,对于 request header 中的 key,WSGI 做如下处理:key = "HTTP_" + k.upper().replace("-","_")

  • start_response : start_response 参数是一个可调用者(callable), 具体参考 the-start-response-callable , 这个是 FC runtime 提供的,它接受两个必要的位置参数和一个可选参数。为方便说明,我们分别将它们命名为 status,response_headers 和 exc_info (注:这并不是说它们一定要用这些名字) , 如下所示:

    1. # Provided by FC runtime.
    2. # status: a string like '200 OK' or '403 FORBIDDEN'
    3. # return: must be a write(body_data) callable
    4. def start_response(status, response_headers, exc_info=None):
    5. ...
    • status : 一个字符串,表示 HTTP 响应状态字符串
    • response_headers : 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示 HTTP 响应的 headers
    • exc_info (可选) : 用于出错时,server 需要返回给浏览器的信息

当 application 对象根据 environ 参数的内容执行完业务逻辑后,就需要返回结果给 server 端。HTTP 的响应需要包含 status,headers 和 body,因此在 application 对象将 body 作为返回值 return 之前,需要先调用 start_response() ,将 status 和 headers 的内容返回给 server,这同时也是告诉 server,application 对象要开始返回 body 了。

获取请求 body

Python runtime 中函数的签名遵循 WSGI 协议规范,您可以直接使用 WSGI 获取 raw body 的方法。

示例代码如下:

  1. # get request_body
  2. try:
  3. request_body_size = int(environ.get('CONTENT_LENGTH', 0))
  4. except (ValueError):
  5. request_body_size = 0
  6. request_body = environ['wsgi.input'].read(request_body_size)

将使用 WSGI 协议的框架部署到函数计算

从上面示例中的Method2,可以看出,利用 Flask、Django 等基于 wsgi 协议的 web frameworks 构建的工程可以运行在函数计算的 python runtime 中,以 Flask 为例:

  1. from flask import Flask
  2. from flask import request
  3. from flask import make_response
  4. app = Flask(__name__)
  5. @app.route('/', methods=['GET', 'POST'])
  6. def home():
  7. resp = make_response('<h1>Home</h1>', 200)
  8. return resp
  9. @app.route('/signin', methods=['GET'])
  10. def signin_form():
  11. # action url 中的service_name,function_name need replace
  12. html = '''<form action="/2016-08-15/proxy/service_name/func_name/signin" method="post">
  13. <p><input name="username"></p>
  14. <p><input name="password" type="password"></p>
  15. <p><button type="submit">Sign In</button></p>
  16. </form>'''
  17. resp = make_response(html, 200)
  18. return resp
  19. @app.route('/signin', methods=['POST'])
  20. def signin():
  21. if request.form['username'] == 'admin' and request.form['password'] == 'password':
  22. html = '<h3>Hello, admin!</h3>'
  23. else:
  24. html = '<h3>Bad username or password.</h3>'
  25. resp = make_response(html, 200)
  26. return resp
  27. @app.route('/signin2', methods=['GET'])
  28. def signin2():
  29. if request.args.get('username') == 'admin' and request.args.get('password') == 'password':
  30. html = '<h3>Hello2, admin!</h3>'
  31. else:
  32. html = '<h3>Bad username or password.</h3>'
  33. resp = make_response(html, 200)
  34. return resp
  35. def handler(environ, start_response):
  36. # maybe pre do something here
  37. return app(environ, start_response)

具体操作步骤可参考 部署基于 python wsgi web 框架的工程到函数计算

关于 Python 运行环境的其他信息,请参考文章 Python 运行环境