本文介绍如何在函数计算控制台编写赋予公网IP地址的函数。

背景信息

本文中函数B代表具有访问控制的第三方应用B,函数A代表需访问第三方应用B的函数计算的应用A。

操作步骤

  1. 登录函数计算控制台
  2. 在左侧导航栏,单击服务及函数
  3. 在顶部菜单栏,选择地域。
  4. 创建函数A。
    1. 服务列表,单击目标服务。
    2. 函数管理页面,单击创建函数
    3. 创建函数页面,选择从零开始创建,然后在基本设置区域,填写函数相关信息后单击创建
      function-a
      参数 是否必填 操作 示例值
      名称 填写自定义的函数名称。必须以字母开头,可包含数字、字母(区分大小写)、下划线(_)和短划线(-),不超过64个字符。
      说明 如果不填写名称,函数计算会自动为您创建。
      Function-A
      运行环境 选择您熟悉的语言,例如Python、Java、PHP、Node.js等。函数计算支持的运行环境,请参见函数简介 Python 3
      函数触发方式 选择函数触发方式。
      • 通过事件触发:通过定时器或其他阿里云服务的触发器来触发函数执行。
      • 通过HTTP请求触发:通过发送HTTP请求触发函数执行,适用于快速构建Web服务等场景。
      通过HTTP请求触发
      实例类型 选择适合您的实例类型。
      • 弹性实例
      • 性能实例
      更多信息,请参见实例类型及使用模式
      弹性实例
      内存规格 设置函数执行内存。
      • 选择输入:在下拉列表中选择所需内存。
      • 手动输入:单击手动输入内存大小,可自定义函数执行内存。内存规格说明如下:
        • 弹性实例:取值范围[128, 3072],单位为MB。
        • 性能实例:取值范围[4, 32],单位为GB。
        说明 输入的内存必须为64 MB的倍数。
      512 MB
    4. 函数代码页签,在代码编辑器中输入以下代码。
      -*- coding: utf-8 -*-
      import logging
      import urllib, urllib2, sys
      import ssl
      import json
      # 代理服务地址。
      proxy_address = 'ip:port'
      # 函数B服务地址。
      data_service_host = 'https://{id}.{region}.fc.aliyuncs.com'
      data_service_path = '/service/path'
      def handler(environ, start_response):
          """
          entrance
          """
          url = data_service_host + data_service_path
          # 使用代理访问。
          proxy_result = get_data_by_url(url, proxy_address)
          # 不使用代理访问。
          normal_result = get_data_by_url(url, None)
          # 用于展示,将两种情况的返回结果拼接后展示出来。
          result = {
              "query_with_proxy_result": proxy_result,
              "query_without_proxy_result": normal_result
          }
          status = '200 OK'
          response_headers = [('Content-type', 'text/json')]
          start_response(status, response_headers)
          return json.dumps(result)
      def get_data_by_url(url, proxy):
          """
          用户数据服务访问封装。
          """
          result = {
              "success": False,
              "secret_data": '',
              "data_service_raw_data": {}
          }
          content = my_http_request(url, proxy)
          if content:
            content = json.loads(content)
          result["data_service_raw_data"] = content
          # 模拟访问后数据处理。
          if "authorized" in content and content["authorized"]:
              result["success"] = True
              result["secret_data"] = content["protected_data"]
          return result
      def my_http_request(url, proxy=None):
          """
          带Proxy的网络请求。
          """
          try:
              ret = None
              socket_fd = None
              request = urllib2.Request(url)
              request.add_header('User-Agent', 'Fake_browser/1.0')
              if proxy:
                  request.set_proxy(proxy, 'http')
              opener = urllib2.build_opener()
              socket_fd = opener.open(request)
              ret = socket_fd.read()
          except Exception as e:
              ret = json.dumps({"info": "exception in proxy query: %s" % e})
          finally:
              if socket_fd:
                  socket_fd.close()
          return ret          

      函数说明如下:

      • handler为函数调用入口,描述了业务逻辑,本例中分别模拟了不经过代理调用函数B及经过代理调用函数B的调用结果。
      • get_data_by_url封装了请求函数B数据的业务逻辑。
      • my_http_request实现了利用代理发送HTTP请求的方式。
  5. 重复上述步骤创建函数B,在函数代码页签,在代码编辑器中输入以下代码。
    说明 函数B使用了Django框架,因此可采用命令行工具Serverless Devs或Python SDK上传代码包,您也可以使用OSS或ZIP包直接上传。

    函数B代码结构如下:

    project/
    ├── lib
    │   ├── Django-1.11.13.dist-info
    │   ├── django
    │   ├── pytz
    │   └── pytz-2018.4.dist-info
    ├── main.py
    └── src
        ├── __init__.py
        ├── bin
        │   ├── __init__.py
        │   └── manage.py
        ├── conf
        │   ├── __init__.py
        │   ├── settings.py
        │   ├── urls.py
        │   └── wsgi.py
        ├── data
        └── views
              ├── __init__.py
              └── view.py            

    Django代码示例如下:

    • 函数入口:
      #!/usr/bin/env python
      # coding=utf-8
      import sys
      import os
      # load local django
      sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)) + '/lib')
      sys.path.append(os.path.join(os.path.dirname(os.path.abspath(__file__)), "src"))
      import django
      print (django.__version__)
      import views.view
      from conf.wsgi import application
      def handler(environ, start_response):
          import urlparse
          parsed_tuple = urlparse.urlparse(environ['fc.request_uri'])
          li = parsed_tuple.path.split('/')
          global base_path
          if not views.view.base_path:
              views.view.base_path = "/".join(li[0:5])
          return application(environ, start_response)            
    • urls.py
      from django.conf.urls import url
      from django.contrib import admin
      from views import view
      urlpatterns = [
          url(r'^index$', view.index),
          url(r'^get_data$', view.get_data),
      ]           
    • views.py
      #!/usr/bin/env python
      # coding=utf-8
      from django.http import HttpResponse
      from django.conf import settings
      import logging
      import json
      logger = logging.getLogger()
      base_path = ""
      def index(request):
          """
          测试入口。
          """
          logger.info("Django request detected! url: index")
          white_list = settings.WHITE_LIST
          allowed_hostlist = ' '
      
          for allowed_host in white_list:
              allowed_hostlist += allowed_host
              allowed_hostlist += ' '
          return HttpResponse("<h1>It works! Copyright: jianyi</h1> White list: %s" % allowed_hostlist, status=200)
      def get_data(request):
          """
          数据获取接口。
          """
          result = {
              "remote_ip": '',
              "white_list": [],
              "authorized": False,
              "protected_data": ''
          }
          if request.META.has_key('HTTP_X_FORWARDED_FOR'):
              remote_ip = request.META['HTTP_X_FORWARDED_FOR']
          else:
              remote_ip = request.META['REMOTE_ADDR']
      
          result["remote_ip"] = remote_ip
          result["white_list"] = settings.WHITE_LIST
      
          if remote_ip in result["white_list"]:
              result["authorized"] = True
              result["protected_data"] = "Alibaba"
      
          return HttpResponse(json.dumps(result), status=200)           
    • settings.py:在默认的settings.py文件结尾增加白名单配置,白名单中添加代理服务器的公网IP地址。
      # User conf
      WHITE_LIST = [
          "127.0.0.1",
          "xx.xx.xx.xx"
      ]           
  6. 搭建并启动Nginx代理。

    Nginx可以部署到ECS中,也可以部署到普通服务器上。更多信息,请参见Nginx安装。安装后,您可以按照以下示例配置文件代理部分。

    server{
        resolver x.x.x.x;
        listen 8080;
        location / {
            proxy_pass http://$http_host$request_uri;
        }
    }           
    注意
    • 文件代理部分不能包含hostname
    • resolver需设置为您的DNS服务器IP地址。
    • $http_host$request_uri为Nginx系统变量,请勿替换。
    • 在实际生产环境中,建议搭建负载均衡集群(利用Nginx、Keepalived等)及代理服务器集群进行代理服务,以提高系统可用性及整体性能。

后续步骤

调试函数