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

背景信息

本文用函数A实现需要访问具有访问控制的服务,用函数B实现具有访问控制的第三方服务。

操作步骤

  1. 登录函数计算控制台
  2. 在顶部菜单栏,选择地域。
  3. 创建函数A。
    1. 单击服务/函数,在服务/函数页面,单击新建函数
    2. 新建函数页面,输入所在服务,选择HTTP函数,单击下一步
      说明 如果输入的服务不存在的话,函数计算会自动创建该服务。
    3. 设置函数A信息,单击创建

      本示例代码基于Python,因此运行环境选择python2.7

      创建函数A
    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请求的方式。
  4. 重复上述步骤创建函数B,在代码执行界面,上传以下代码。
    说明 函数B使用了Django框架,因此可采用命令行工具fcli或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"
      ]           
  5. 配置触发器。具体步骤,请参见创建触发器
  6. 搭建并启动Nginx代理。

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

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

后续步骤

调试函数