Repository: aliyun/fun
Pull request #144 (2019-01-28 08:25:41)

enhance api gateway support


Patch:
diff --git a/docs/specs/2018-04-03-zh-cn.md b/docs/specs/2018-04-03-zh-cn.md
index 96f43e6..1819b3d 100644
--- a/docs/specs/2018-04-03-zh-cn.md
+++ b/docs/specs/2018-04-03-zh-cn.md
@@ -163,6 +163,8 @@ StageName | `string` | **必填。** 阶段的名称，API网关用作调用统
 DefinitionUri | `string` | 描述 API 的 Swagger 文档地址。必须指定 `DefinitionUri` 或 `DefinitionBody` 中的任何一个。
 DefinitionBody | `YAML 对象` | 描述您的 API 的 Swagger 规范。必须指定 `DefinitionUri` 或 `DefinitionBody` 中的任何一个。
 
+`DefinitionBody` 可以参考: [https://github.com/aliyun/fun/tree/master/examples/api-gateway/template.yml](https://github.com/aliyun/fun/tree/master/examples/api-gateway/template.yml)
+
 ##### 示例：Aliyun::Serverless::Api
 
 ```yaml
diff --git a/docs/specs/2018-04-03.md b/docs/specs/2018-04-03.md
index d2729e6..1c2d444 100644
--- a/docs/specs/2018-04-03.md
+++ b/docs/specs/2018-04-03.md
@@ -159,6 +159,8 @@ StageName | `string` | **Required.** The name of the stage, which API Gateway us
 DefinitionUri | `string` | Swagger document address describing the API. You must specify either `DefinitionUri` or `DefinitionBody`.
 DefinitionBody | `JSON or YAML Object` | Swagger specification that describes your API. Either one of `DefinitionUri` or `DefinitionBody` must be specified.
 
+`DefinitionBody` can refer to: [https://github.com/aliyun/fun/tree/master/examples/api-gateway/template.yml](https://github.com/aliyun/fun/tree/master/examples/api-gateway/template.yml).
+
 ##### Example: Aliyun::Serverless::Api
 
 ```yaml
diff --git a/examples/api-gateway/index.js b/examples/api-gateway/index.js
new file mode 100644
index 0000000..ecd56ca
--- /dev/null
+++ b/examples/api-gateway/index.js
@@ -0,0 +1,49 @@
+
+// https://help.aliyun.com/document_detail/54788.html
+module.exports.handler = function (event, context, callback) {
+
+    var responseCode = 200;
+
+    console.log("request: " + JSON.stringify(event.toString()));
+
+    // 将 event 转化为 JSON 对象
+    event = JSON.parse(event.toString());
+
+    // 根据用户输入的 statusCode 返回，可用于测试不同 statusCode 的情况
+    if (event.queryParameters !== null && event.queryParameters !== undefined) {
+        if (event.queryParameters.httpStatus !== undefined && event.queryParameters.httpStatus !== null && event.queryParameters.httpStatus !== "") {
+            console.log("Received http status: " + event.queryParameters.httpStatus);
+            responseCode = event.queryParameters.httpStatus;
+        }
+    }
+
+    // 如果 body 是 Base64 编码的，FC 中需要对 body 内容进行解码
+    if (event.body !== null && event.body !== undefined) {
+        if (event.isBase64Encoded !== null && event.isBase64Encoded !== undefined && event.isBase64Encoded) {
+            event.body = new Buffer(event.body, 'base64').toString();
+        }
+    }
+
+    // input 是 API 网关给 FC 的输入内容
+    var responseBody = {
+        message: "Hello World!",
+        input: event
+    };
+
+    // 对 body 内容进行 Base64 编码，可根据需要处理
+    var base64EncodeStr = new Buffer(JSON.stringify(responseBody)).toString('base64');
+
+    // FC 给 API 网关返回的格式，须如下所示。isBase64Encoded 根据 body 是否 Base64 编码情况设置
+    var response = {
+        isBase64Encoded: true,
+        statusCode: responseCode,
+        headers: {
+            "x-custom-header": "header value"
+        },
+        body: base64EncodeStr
+    };
+    
+    console.log("response: " + JSON.stringify(response));
+
+    callback(null, response);
+}
\ No newline at end of file
diff --git a/examples/api-gateway/template.yml b/examples/api-gateway/template.yml
new file mode 100644
index 0000000..0c601f4
--- /dev/null
+++ b/examples/api-gateway/template.yml
@@ -0,0 +1,106 @@
+ROSTemplateFormatVersion: '2015-09-01'
+Transform: 'Aliyun::Serverless-2018-04-03'
+Resources:
+  fc:
+    Type: 'Aliyun::Serverless::Service'
+    Properties:
+      Description: 'fc test'
+    apigateway:
+      Type: 'Aliyun::Serverless::Function'
+      Properties:
+        Handler: index.handler
+        Runtime: nodejs8
+        CodeUri: './index.js'
+        Timeout: 60
+
+  HelloworldGroup:
+    Type: 'Aliyun::Serverless::Api'
+    Properties:
+      StageName: RELEASE
+      DefinitionBody:
+        '/':
+          get: # more info read: [Chinese](https://help.aliyun.com/document_detail/43623.html), [English](https://www.alibabacloud.com/help/doc-detail/43623.htm)
+            x-aliyun-apigateway-api-name: hello_get
+            x-aliyun-apigateway-fc:
+              arn: acs:fc:::services/${fc.Arn}/functions/${apigateway.Arn}/
+              timeout: 10000
+
+            # RequestConfig document link: [Chinese](https://help.aliyun.com/document_detail/43985.html), [English](https://www.alibabacloud.com/help/doc-detail/43985.htm)
+            x-aliyun-apigateway-request-config: 
+              requestMode: "MAPPING" # if dont need care parameters, use `PASSTHROUGH` instead
+              requestProtocol: "http"
+
+            # RequestParameter document link: [Chinese](https://help.aliyun.com/document_detail/43986.html), [English](https://www.alibabacloud.com/help/doc-detail/43986.htm)
+            # here is a simplest request parameters demo
+            # requestMode shoud to be `MAPPING` when specificing query parameters
+            x-aliyun-apigateway-request-parameters:
+              - apiParameterName: 'token'
+                location: 'Query'
+                parameterType: 'String'
+                required: 'REQUIRED'
+              - apiParameterName: 'token2'
+                location: 'Query'
+                parameterType: 'String'
+                required: 'REQUIRED'
+                
+            # here is how to custom service parameters, for example add a constant parameter
+            # x-aliyun-apigateway-request-parameters:
+            #   - apiParameterName: 'token'
+            #     location: 'Query'
+            #     parameterType: 'String'
+            #     required: 'REQUIRED'
+            #     defaultValue: 'e'
+            # x-aliyun-apigateway-service-parameters:
+            #   - serviceParameterName: 'token'
+            #     location: 'Query'
+            #     parameterCatalog: 'CONSTANT'
+
+            # here is how to custom service parameters, for example add a SYSTEM parameter
+            # x-aliyun-apigateway-request-parameters:
+            #   - apiParameterName: 'CaClientIp'
+            #     location: 'Query'
+            #     parameterType: 'String'
+            #     required: 'REQUIRED'
+            # x-aliyun-apigateway-service-parameters:
+            #   - serviceParameterName: 'CaClientIp'
+            #     location: 'Query'
+            #     parameterCatalog: 'SYSTEM'   
+
+  # ============================= another exmaple for advanced usage =============================
+  # HelloworldGroup:
+  #   Type: 'Aliyun::Serverless::Api'
+  #   Properties:
+  #     StageName: RELEASE
+  #     DefinitionBody:
+  #       '/':
+  #         get:
+  #           x-aliyun-apigateway-api-name: hello_get
+  #           x-aliyun-apigateway-fc:
+  #             arn: acs:fc:::services/${fc.Arn}/functions/${apigateway.Arn}/
+  #             timeout: 4000 # ms
+  #           x-aliyun-apigateway-request-config: 
+  #             requestMode: "MAPPING" 
+  #             requestProtocol: "http"
+  #           x-aliyun-apigateway-request-parameters:
+  #             - apiParameterName: 'token'
+  #               location: 'Query'
+  #               parameterType: 'String'
+  #               required: 'REQUIRED'
+  #             - apiParameterName: 'param'
+  #               location: 'Query'
+  #               parameterType: 'String'
+  #               required: 'REQUIRED'
+  #           x-aliyun-apigateway-service-parameters:
+  #             - serviceParameterName: 'renamedToken'
+  #               location: 'Query'
+  #               parameterCatalog: 'REQUEST'
+  #             - serviceParameterName: 'renamedParam'
+  #               location: 'Query'
+  #               parameterCatalog: 'REQUEST'
+
+  #           # ServiceParameterMap document link: [Chinese](https://help.aliyun.com/document_detail/43989.html), [English](https://www.alibabacloud.com/help/doc-detail/43989.htm)
+  #           x-aliyun-apigateway-service-parameters-map:
+  #             - requestParameterName: 'token'
+  #               serviceParameterName: 'renamedToken'
+  #             - requestParameterName: 'param'     
+  #               serviceParameterName: 'renamedParam'
diff --git a/examples/openid_connect/template.yml b/examples/openid_connect/template.yml
index 75abafd..b68fb82 100644
--- a/examples/openid_connect/template.yml
+++ b/examples/openid_connect/template.yml
@@ -24,8 +24,8 @@ Resources:
             x-aliyun-apigateway-request-parameters:
               - apiParameterName: 'token'
                 location: 'Path'
-                type: 'String'
-                parameterType: 'REQUIRED'
+                parameterType: 'String'
+                required: 'REQUIRED'
             x-aliyun-apigateway-visibility: PRIVATE
             x-aliyun-apigateway-auth-type: 'APPOPENID'
             x-aliyun-apigateway-open-id-connect-config:
diff --git a/lib/commands/config.js b/lib/commands/config.js
index 204f3cb..12f587e 100644
--- a/lib/commands/config.js
+++ b/lib/commands/config.js
@@ -41,10 +41,10 @@ async function config() {
       name: 'defaultRegion',
       message: 'Default region name',
       choices: ['cn-qingdao', 'cn-beijing', 'cn-zhangjiakou', 
-                'cn-hangzhou', 'cn-shanghai', 'cn-shenzhen' , 
-                'cn-hongkong', 'ap-southeast-1', 'ap-southeast-2', 
-                'ap-northeast-1', 'us-west-1', 'us-east-1', 
-                'eu-central-1', 'ap-south-1'],
+        'cn-hangzhou', 'cn-shanghai', 'cn-shenzhen' , 
+        'cn-hongkong', 'ap-southeast-1', 'ap-southeast-2', 
+        'ap-northeast-1', 'us-west-1', 'us-east-1', 
+        'eu-central-1', 'ap-south-1'],
       default: profile.defaultRegion
     },
     {
diff --git a/lib/deploy/deploy-by-tpl.js b/lib/deploy/deploy-by-tpl.js
index 3286362..a16cf4a 100644
--- a/lib/deploy/deploy-by-tpl.js
+++ b/lib/deploy/deploy-by-tpl.js
@@ -426,7 +426,9 @@ async function deployApigateway(name, { apiDefinition, template, tplPath }) {
             openIdConnectConfig = methodDefinition['x-aliyun-apigateway-openid-connect-config'];
           }
 
-          const constantParameters = methodDefinition['x-aliyun-apigateway-constant-parameters'];
+          const requestParameters = methodDefinition['x-aliyun-apigateway-request-parameters'];
+          const serviceParameters = methodDefinition['x-aliyun-apigateway-service-parameters'];
+          const serviceParametersMap = methodDefinition['x-aliyun-apigateway-service-parameters-map'];
 
           await makeApi(apiGroup, {
             stageName: apiDefinition.Properties.StageName,
@@ -437,7 +439,9 @@ async function deployApigateway(name, { apiDefinition, template, tplPath }) {
             serviceName,
             functionName,
             serviceTimeout,
-            parameters: methodDefinition['x-aliyun-apigateway-request-parameters'],
+            requestParameters,
+            serviceParameters,
+            serviceParametersMap,
             auth: {
               type: methodDefinition['x-aliyun-apigateway-auth-type'],
               config: openIdConnectConfig,
@@ -445,7 +449,6 @@ async function deployApigateway(name, { apiDefinition, template, tplPath }) {
             visibility: methodDefinition['x-aliyun-apigateway-visibility'],
             requestConfig,
             resultConfig,
-            constantParameters,
             description: methodDefinition['x-aliyun-apigateway-description']
           });
         }
diff --git a/lib/deploy/deploy-support-api.js b/lib/deploy/deploy-support-api.js
new file mode 100644
index 0000000..ce8fcf1
--- /dev/null
+++ b/lib/deploy/deploy-support-api.js
@@ -0,0 +1,91 @@
+'use strict';
+
+const _ = require('lodash');
+
+// generate service parameter using request parameter
+// https://help.aliyun.com/document_detail/43988.html
+function generateServiceParameter(serviceParameterName, requestParameter) {
+  return {
+    serviceParameterName: serviceParameterName,
+    location: requestParameter.location,
+    parameterType: requestParameter.parameterType,
+    parameterCatalog: 'REQUEST'
+  };
+}
+
+// generate service ServiceParameterMap
+// https://help.aliyun.com/document_detail/43989.html
+function generateServiceParameterMap(requestParameterName, serviceParameterName) {
+  return {
+    serviceParameterName: serviceParameterName,
+    requestParameterName: requestParameterName
+  };
+}
+
+function getDefaultRequestParameter() {
+  return {
+    'location': 'QUERY',
+    'parameterType': 'String',
+    'required': 'OPTIONAL'
+  };
+}
+
+function assertArray(arg, errorMessage) {
+  if (arg !== null && arg !== undefined && !Array.isArray(arg)) {
+    throw new Error(errorMessage);
+  }
+}
+
+function processApiParameters(requestParameters = [], serviceParameters = [], serviceParametersMap = []) {
+
+  assertArray(requestParameters, 'requestParameters must be array');
+  assertArray(serviceParameters, 'serviceParameters must be array');
+  assertArray(serviceParametersMap, 'serviceParametersMap must be array');
+
+  // 1. 初始化
+  const apiRequestParameters = requestParameters.map((item) => {
+    return Object.assign(getDefaultRequestParameter(), item);
+  });
+
+  // 下面的处理保证用户填写的配置不会被修改，但会尽可能推测未配置项
+  const apiServiceParameters = (serviceParameters || []).slice(0);
+  const apiServiceParametersMap = (serviceParametersMap || []).slice(0);
+
+  // 2. 如果配置了 requestParameters，则遍历所有 reqeustParameter
+  apiRequestParameters.forEach(requestParameter => {
+    const serviceParameterMap = _.find(serviceParametersMap, { 'requestParameterName': requestParameter.apiParameterName });
+    if (serviceParameterMap) {
+      // 2.1 如果在 serviceParametersMap 中存在该 reqeustParameter，则使用 ServiceParameterName 检查 serviceParameters 是否存在
+      const serviceParameter = _.find(serviceParameters, { 'serviceParameterName': serviceParameterMap.serviceParameterName });
+      // 2.1.1 如果存在，则忽略
+      // 2.1.2 如果不存在，则自动生成配置，且 ServiceParameterName 配置为 serviceParametersMap 的值
+      if (!serviceParameter) {
+        const apiServieParameter = generateServiceParameter(serviceParameterMap.serviceParameterName, requestParameter);
+        apiServiceParameters.push(apiServieParameter);
+      }
+    } else {
+      // 2.2 如果在 serviceParametersMap 中不存在 reqeustParameter，则检查 reqeustParameter 的 apiParameterName 是否在 serviceParameters 中存在：
+      const serviceParameter = _.find(serviceParameters, { 'serviceParameterName': requestParameter.apiParameterName });
+      if (serviceParameter) {
+        // 2.2.1 如果存在，则忽略，但会配置 serviceParametersMap
+        const serviceParameterMap = generateServiceParameterMap(requestParameter.apiParameterName, requestParameter.apiParameterName);
+        apiServiceParametersMap.push(serviceParameterMap);
+      } else {
+        // 2.2.2 如果不存在，则自动创建，且 ServiceParameterName 配置为 apiParameterName，且配置 serviceParametersMap
+        const apiServieParameter = generateServiceParameter(requestParameter.apiParameterName, requestParameter);
+        apiServiceParameters.push(apiServieParameter);
+
+        const serviceParameterMap = generateServiceParameterMap(requestParameter.apiParameterName, requestParameter.apiParameterName);
+        apiServiceParametersMap.push(serviceParameterMap);
+      }
+    }
+  });
+
+  return {
+    apiRequestParameters,
+    apiServiceParameters,
+    apiServiceParametersMap
+  };
+}
+
+module.exports = { processApiParameters };
\ No newline at end of file
diff --git a/lib/deploy/deploy-support.js b/lib/deploy/deploy-support.js
index 44210c6..45afd0f 100644
--- a/lib/deploy/deploy-support.js
+++ b/lib/deploy/deploy-support.js
@@ -16,6 +16,7 @@ const debug = require('debug')('fun:deploy');
 const osLocale = require('os-locale');
 const MNSClient = require('@alicloud/mns');
 const path = require('path');
+const { processApiParameters } = require('./deploy-support-api');
 
 const hashedMachineId = require('node-machine-id').machineId;
 
@@ -353,9 +354,9 @@ async function makeService({
 function generateFunIngore(baseDir, codeUri) {
   const absCodeUri = path.resolve(codeUri);
   const absBaseDir = path.resolve(baseDir);
-  
+
   const relative = path.relative(absBaseDir, absCodeUri);
-  
+
   if (codeUri.startsWith('..') || relative.startsWith('..')) {
     console.warn(red(`\t\twarning: funignore is not supported for your CodeUri: ${codeUri}`));
     return null;
@@ -820,14 +821,6 @@ function getDefaultRequestConfig(method, requestPath) {
   };
 }
 
-function getDefaultRequestParameter() {
-  return {
-    'location': 'QUERY',
-    'parameterType': 'String',
-    'required': 'OPTIONAL'
-  };
-}
-
 async function makeApi(group, {
   stageName,
   requestPath,
@@ -837,12 +830,13 @@ async function makeApi(group, {
   serviceName,
   functionName,
   serviceTimeout = 3000,
-  parameters = [],
   auth = {},
   visibility = 'Private',
   requestConfig = {},
   resultConfig = {},
-  constantParameters,
+  requestParameters = [],
+  serviceParameters = [],
+  serviceParametersMap = [],
   description
 }) {
   await promiseRetry(async (retry, times) => {
@@ -863,31 +857,15 @@ async function makeApi(group, {
         api = apiSummarys.find(summary => summary.ApiName === apiName);
       }
 
-      const requestParameters = parameters.map((item) => {
-        return Object.assign(getDefaultRequestParameter(), item);
-      });
-
-      const serviceParameters = requestParameters.map((item) => {
-        return {
-          ServiceParameterName: item.apiParameterName,
-          Location: item.location,
-          ParameterType: item.parameterType,
-          ParameterCatalog: 'REQUEST'
-        };
-      });
-
-      const serviceParametersMap = requestParameters.map((item) => {
-        return {
-          ServiceParameterName: item.apiParameterName,
-          RequestParameterName: item.apiParameterName
-        };
-      });
-
       const mergedRequestConfig = Object.assign(getDefaultRequestConfig(method), {
-        'RequestHttpMethod': method,
-        'RequestPath': requestPath
+        'requestHttpMethod': method,
+        'requestPath': requestPath
       }, requestConfig);
 
+      const { apiRequestParameters, 
+        apiServiceParameters, 
+        apiServiceParametersMap} = processApiParameters(requestParameters, serviceParameters, serviceParametersMap);
+
       const profile = await getProfile();
       var params = {
         GroupId: group.GroupId,
@@ -896,9 +874,9 @@ async function makeApi(group, {
         Description: description || 'The awesome api generated by fun',
         AuthType: auth.type || 'ANONYMOUS',
         RequestConfig: JSON.stringify(mergedRequestConfig),
-        RequestParameters: JSON.stringify(requestParameters),
-        ServiceParameters: JSON.stringify(serviceParameters),
-        ServiceParametersMap: JSON.stringify(serviceParametersMap),
+        RequestParameters: JSON.stringify(apiRequestParameters),
+        ServiceParameters: JSON.stringify(apiServiceParameters),
+        ServiceParametersMap: JSON.stringify(apiServiceParametersMap),
         ServiceConfig: JSON.stringify({
           'ServiceProtocol': 'FunctionCompute',
           'ContentTypeValue': 'application/x-www-form-urlencoded; charset=UTF-8',
@@ -922,10 +900,6 @@ async function makeApi(group, {
         FailResultSample: resultConfig.failResultSample || 'failed samples'
       };
 
-      if (constantParameters) {
-        params.ConstantParameters = JSON.stringify(constantParameters);
-      }
-
       if (auth.type === 'APPOPENID' || auth.type === 'OPENID') {
         var openidConf = auth.config || {};
         params.OpenIdConnectConfig = JSON.stringify({
diff --git a/package-lock.json b/package-lock.json
index dd97c6d..325de10 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -606,7 +606,7 @@
     },
     "buffer": {
       "version": "4.9.1",
-      "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
+      "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
       "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=",
       "requires": {
         "base64-js": "^1.0.2",
@@ -635,6 +635,15 @@
       "integrity": "sha1-dBxSFkaOrcRXsDQQEYrXfejB3bE=",
       "dev": true
     },
+    "bytebuffer": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-4.1.0.tgz",
+      "integrity": "sha1-TFgmngUqseSx9/82T9+zzogpBqo=",
+      "requires": {
+        "bufferview": "~1",
+        "long": "~2 >=2.3.0"
+      }
+    },
     "bytes": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz",
@@ -2341,7 +2350,7 @@
     },
     "int64-buffer": {
       "version": "0.1.9",
-      "resolved": "http://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.9.tgz",
+      "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-0.1.9.tgz",
       "integrity": "sha1-ngOdoEOyT3ixlrKD4EZT716ZD2E="
     },
     "into-stream": {
@@ -2764,6 +2773,11 @@
       "integrity": "sha512-l9x0+1offnKKIzYVjyXU2SiwhXDLekRzKyhnbyldPHvC7BvLPVpdNUNR2KeMAiCN2D/kLNttZgQD5WjSxuBx3Q==",
       "dev": true
     },
+    "long": {
+      "version": "2.4.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz",
+      "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8="
+    },
     "lru-cache": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz",
@@ -3594,6 +3608,31 @@
       "integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=",
       "dev": true
     },
+    "protobufjs": {
+      "version": "4.1.3",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-4.1.3.tgz",
+      "integrity": "sha1-jjbRsCJsu2jWR+S0TCoUTzfyd54=",
+      "requires": {
+        "ascli": "~1",
+        "bytebuffer": "~4 >=4.1",
+        "glob": "^5.0.10",
+        "yargs": "^3.10.0"
+      },
+      "dependencies": {
+        "glob": {
+          "version": "5.0.15",
+          "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
+          "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
+          "requires": {
+            "inflight": "^1.0.4",
+            "inherits": "2",
+            "minimatch": "2 || 3",
+            "once": "^1.3.0",
+            "path-is-absolute": "^1.0.0"
+          }
+        }
+      }
+    },
     "proxy-addr": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz",
@@ -4348,52 +4387,13 @@
       }
     },
     "tablestore": {
-      "version": "4.1.0",
-      "resolved": "https://registry.npmjs.org/tablestore/-/tablestore-4.1.0.tgz",
-      "integrity": "sha512-KrbX9skzn/KKAnLpQAblJ8c12XeURJKkhOrcYnKCKThronsViAFvUAptexqgW5fnvBM1gmkX5k6bQW6sX/QafA==",
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/tablestore/-/tablestore-4.2.0.tgz",
+      "integrity": "sha512-usAhh7VA+ilP5qMNLFuj8V6NVbbCxeofHSjVkkFBJ1E4AZth//jC4k8O8n5ijKdee3ImboMrmJPs3GmjvBbObA==",
       "requires": {
         "buffer": "4.9.1",
         "int64-buffer": "0.1.9",
         "protobufjs": "^4.1.2"
-      },
-      "dependencies": {
-        "bytebuffer": {
-          "version": "4.1.0",
-          "resolved": "https://registry.npmjs.org/bytebuffer/-/bytebuffer-4.1.0.tgz",
-          "integrity": "sha1-TFgmngUqseSx9/82T9+zzogpBqo=",
-          "requires": {
-            "bufferview": "~1",
-            "long": "~2 >=2.3.0"
-          }
-        },
-        "glob": {
-          "version": "5.0.15",
-          "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz",
-          "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=",
-          "requires": {
-            "inflight": "^1.0.4",
-            "inherits": "2",
-            "minimatch": "2 || 3",
-            "once": "^1.3.0",
-            "path-is-absolute": "^1.0.0"
-          }
-        },
-        "long": {
-          "version": "2.4.0",
-          "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz",
-          "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8="
-        },
-        "protobufjs": {
-          "version": "4.1.3",
-          "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-4.1.3.tgz",
-          "integrity": "sha1-jjbRsCJsu2jWR+S0TCoUTzfyd54=",
-          "requires": {
-            "ascli": "~1",
-            "bytebuffer": "~4 >=4.1",
-            "glob": "^5.0.10",
-            "yargs": "^3.10.0"
-          }
-        }
       }
     },
     "tar-fs": {
diff --git a/package.json b/package.json
index f8325ce..d39091d 100644
--- a/package.json
+++ b/package.json
@@ -67,7 +67,7 @@
     "require-from-string": "^2.0.2",
     "rimraf": "^2.6.2",
     "semver": "^5.5.0",
-    "tablestore": "^4.0.14",
+    "tablestore": "^4.2.0",
     "urlencode": "^1.1.0"
   },
   "files": [


Test patch:
diff --git a/test/deploy/deploy-by-tpl.test.js b/test/deploy/deploy-by-tpl.test.js
index 2cc88b3..14c7041 100644
--- a/test/deploy/deploy-by-tpl.test.js
+++ b/test/deploy/deploy-by-tpl.test.js
@@ -235,11 +235,11 @@ describe('deploy', () => {
       description: undefined,
       functionName: 'helloworld',
       method: 'get',
-      parameters: [{
+      requestParameters: [{
         location: 'Path',
         apiParameterName: 'token',
-        parameterType: 'REQUIRED',
-        type: 'String'
+        parameterType: 'String',
+        required: 'REQUIRED'
       }],
       requestPath: '/getUserInfo/[token]',
       roleArn: `acs:ram::123:role/aliyunfcgeneratedrole-fc`,
@@ -247,7 +247,8 @@ describe('deploy', () => {
       stageName: 'RELEASE',
       visibility: 'PRIVATE',
       serviceTimeout: 3000,
-      constantParameters: undefined,
+      serviceParameters: undefined,
+      serviceParametersMap: undefined,
       resultConfig: { failResultSample: undefined, resultSample: undefined, resultType: undefined },
       requestConfig: {}
     });
@@ -448,7 +449,7 @@ describe('deploy', () => {
       description: undefined,
       functionName: 'hello',
       method: 'get',
-      parameters: undefined,
+      requestParameters: undefined,
       requestPath: '/python/hello',
       roleArn: `acs:ram::123:role/aliyunfcgeneratedrole-fc`,
       serviceName: 'pythondemo',
@@ -456,7 +457,8 @@ describe('deploy', () => {
       visibility: undefined,
       serviceTimeout: 3000,
       requestConfig: {},
-      constantParameters: undefined,
+      serviceParameters: undefined,
+      serviceParametersMap: undefined,      
       resultConfig: { failResultSample: undefined, resultSample: undefined, resultType: undefined },
     });
   });
@@ -498,14 +500,15 @@ describe('deploy', () => {
       auth: { config: undefined, type: undefined },
       functionName: 'doSegment',
       method: 'post',
-      parameters: undefined,
+      requestParameters: undefined,
       requestPath: '/do_segment',
       roleArn: `acs:ram::123:role/aliyunfcgeneratedrole-fc`,
       serviceName: 'maas',
       stageName: 'RELEASE',
       visibility: undefined,
       resultConfig: { failResultSample: undefined, resultSample: undefined, resultType: undefined },
-      constantParameters: undefined,
+      serviceParameters: undefined,
+      serviceParametersMap: undefined,
       serviceTimeout: 3000,
       requestConfig: { requestMode: 'PASSTHROUGH', requestProtocol: 'http' }
     });
@@ -584,7 +587,7 @@ describe('deploy', () => {
       functionName: 'get',
       method: 'get',
       description: undefined,
-      parameters: [
+      requestParameters: [
         { apiParameterName: 'encrypt_type' },
         { apiParameterName: 'msg_signature' },
         { location: 'Query', apiParameterName: 'timestamp', required: 'REQUIRED', parameterType: 'String' },
@@ -596,7 +599,8 @@ describe('deploy', () => {
       roleArn: `acs:ram::123:role/aliyunfcgeneratedrole-fc`,
       serviceName: 'wechat',
       stageName: 'RELEASE',
-      constantParameters: undefined,
+      serviceParameters: undefined,
+      serviceParametersMap: undefined,
       visibility: undefined,
       requestConfig: { bodyFormat: 'STREAM', requestMode: 'MAPPING', requestProtocol: 'HTTP' },
       resultConfig: { failResultSample: undefined, resultSample: undefined, resultType: undefined },
@@ -623,7 +627,7 @@ describe('deploy', () => {
       functionName: 'post',
       method: 'post',
       description: undefined,
-      parameters: [
+      requestParameters: [
         { location: 'Query', apiParameterName: 'timestamp', required: 'REQUIRED', parameterType: 'String' },
         { location: 'Query', apiParameterName: 'nonce', parameterType: 'String' },
         { location: 'Query', apiParameterName: 'signature', parameterType: 'String' },
@@ -635,7 +639,8 @@ describe('deploy', () => {
       serviceName: 'wechat',
       stageName: 'RELEASE',
       visibility: undefined,
-      constantParameters: undefined,
+      serviceParameters: undefined,
+      serviceParametersMap: undefined,
       requestConfig: { bodyFormat: 'STREAM', requestMode: 'MAPPING', requestProtocol: 'HTTP' },
       resultConfig: { failResultSample: undefined, resultSample: undefined, resultType: undefined },
       serviceTimeout: 3000
@@ -703,7 +708,7 @@ describe('deploy', () => {
       description: undefined,
       functionName: 'initializer',
       method: 'get',
-      parameters: undefined,
+      requestParameters: undefined,
       requestPath: '/python/initializer',
       roleArn: `acs:ram::123:role/aliyunfcgeneratedrole-fc`,
       serviceName: 'initializerdemo',
@@ -711,7 +716,8 @@ describe('deploy', () => {
       visibility: undefined,
       serviceTimeout: 3000,
       requestConfig: {},
-      constantParameters: undefined,
+      serviceParameters: undefined,
+      serviceParametersMap: undefined,
       resultConfig: { failResultSample: undefined, resultSample: undefined, resultType: undefined },
     });
   });
diff --git a/test/deploy/deploy-support-api.test.js b/test/deploy/deploy-support-api.test.js
new file mode 100644
index 0000000..56f6c90
--- /dev/null
+++ b/test/deploy/deploy-support-api.test.js
@@ -0,0 +1,293 @@
+'use strict';
+
+const expect = require('expect.js');
+const { processApiParameters } = require('./../../lib/deploy/deploy-support-api');
+
+describe('test process api parameters', () => {
+
+  var requestParameters;
+  var serviceParameters;
+  var serviceParametersMap;
+
+  beforeEach(() => {
+    requestParameters = [
+      {
+        apiParameterName: 'token',
+        location: 'Query',
+        parameterType: 'String',
+        required: 'REQUIRED',
+        defaultValue: 'e'
+      }
+    ];
+
+    serviceParameters = [{
+      serviceParameterName: 'token',
+      location: 'PATH',
+      parameterType: 'STRING',
+      parameterCatalog: 'CONSTANT'
+    }];
+
+    serviceParametersMap = [
+      {
+        serviceParameterName: 'token',
+        requestParameterName: 'token'
+      }
+    ];
+  });
+
+  it('test only requestParameters', () => {
+
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, null, null);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'token',
+      location: 'Query',
+      parameterType: 'String',
+      parameterCatalog: 'REQUEST'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'token',
+      requestParameterName: 'token'
+    }]);
+  });
+
+  it('test only request parameters and mached service parameters', () => {
+
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, serviceParameters, null);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'token',
+      location: 'PATH',
+      parameterType: 'STRING',
+      parameterCatalog: 'CONSTANT'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'token',
+      requestParameterName: 'token'
+    }]);
+  });
+
+  it('test only request parameters and serviceParametersMap', () => {
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, null, serviceParametersMap);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'token',
+      location: 'Query',
+      parameterType: 'String',
+      parameterCatalog: 'REQUEST'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'token',
+      requestParameterName: 'token'
+    }]);
+  });
+
+  it('test mached requestParameters, serviceParameters and serviceParametersMap', () => {
+
+    serviceParametersMap[0].serviceParameterName = 'rename';
+
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, null, serviceParametersMap);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'rename',
+      location: 'Query',
+      parameterType: 'String',
+      parameterCatalog: 'REQUEST'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'rename',
+      requestParameterName: 'token'
+    }]);
+  });
+
+  it('test requestParameters, renamed serviceParameters', () => {
+
+    serviceParameters[0].serviceParameterName = 'rename';
+
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, serviceParameters, null);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'rename',
+      location: 'PATH',
+      parameterType: 'STRING',
+      parameterCatalog: 'CONSTANT'
+    },
+    {
+      serviceParameterName: 'token',
+      location: 'Query',
+      parameterType: 'String',
+      parameterCatalog: 'REQUEST'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'token',
+      requestParameterName: 'token'
+    }]);
+  });
+
+  it('test requestParameters, renamed serviceParametersMap', () => {
+
+    serviceParametersMap[0].serviceParameterName = 'renamed';
+
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, null, serviceParametersMap);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'renamed',
+      location: 'Query',
+      parameterType: 'String',
+      parameterCatalog: 'REQUEST'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'renamed',
+      requestParameterName: 'token'
+    }]);
+  });
+
+  it('test renamed requestParameters, renamed again serviceParameters, renamed serviceParametersMap', () => {
+
+    serviceParametersMap[0].serviceParameterName = 'renamed';
+    serviceParameters[0].serviceParameterName = 'reanmed again';
+
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, serviceParameters, serviceParametersMap);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'reanmed again',
+      location: 'PATH',
+      parameterType: 'STRING',
+      parameterCatalog: 'CONSTANT'
+    },
+    {
+      serviceParameterName: 'renamed',
+      location: 'Query',
+      parameterType: 'String',
+      parameterCatalog: 'REQUEST'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'renamed',
+      requestParameterName: 'token'
+    }]);
+  });
+
+
+  it('test requestParameters, renamed serviceParameters, renamed serviceParametersMap, but do nothing', () => {
+
+    serviceParametersMap[0].serviceParameterName = 'renamed';
+    serviceParameters[0].serviceParameterName = 'renamed';
+
+    const {
+      apiRequestParameters,
+      apiServiceParameters,
+      apiServiceParametersMap
+    } = processApiParameters(requestParameters, serviceParameters, serviceParametersMap);
+
+    expect(apiRequestParameters).to.eql([{
+      location: 'Query',
+      parameterType: 'String',
+      required: 'REQUIRED',
+      apiParameterName: 'token',
+      defaultValue: 'e'
+    }]);
+
+    expect(apiServiceParameters).to.eql([{
+      serviceParameterName: 'renamed',
+      location: 'PATH',
+      parameterType: 'STRING',
+      parameterCatalog: 'CONSTANT'
+    }]);
+
+    expect(apiServiceParametersMap).to.eql([{
+      serviceParameterName: 'renamed',
+      requestParameterName: 'token'
+    }]);
+  });
+});
\ No newline at end of file