API网关管理
[TOC]
# 总体介绍
proxy阶段是生命周期的第2步,主要功能是对请求参数进行剪裁和变形。

proxy阶段主要完成以下功能:
**1.输入转发字段映射**
**2.系统字段转发**
**3.合成字段配置**
**4.代码转换**
# 1. 输入转发字段映射
整体示意图示下:

假设有一个接口,它定义了这样三个参数:

这个接口上线,客户端也在正常调用了,但是后端服务由于某些原因,把原来的[my_para]参数改为了[new_para]参数。这样就导至大面积的客户端请求失败,直到这个时候服务方才发现【动了不该动的东西,惹了不该惹的人】,但事已至此,如何解决呢?通常的做法是这样:
1. 服务商修改程序,重启。可以同时接受my_para和new_para参数,这样客户端的代码都不用改。
2. 客户端修改程序,重启。把原来传的my_para改为new_para,大面积重启客户端程序。
可以看到这二种方式都很痛苦。有没有更好的办法呢?有的,使用易源【输入转发字段映射】,1分钟解决。

经过易源的一个映射操作,客户端和服务方都不用做调整了,业务顺利进行。
# 2. 系统字段转发
用户提交信息到易源,易源再将信息转发至后端服务。
如果后端服务需要了解调用者的更多信息,那易源就得要转发更多信息到后端。其流程示意图示下:

配置界面如下:

# 3. 合成字段配置
把多个字段变量合成一个新的字段值。其逻辑图如下:

根据输入参数+环境变量,合成新的参数。图中的{{@变量}}是mustache语法,表明其会应用【反sql注射】操作。
## 3.1 使用场景1
假设这么样的一个接口,传给它一个sql语句,接口执行这个sql,然后把内容返回。这个sql可能会是:
```sql
select * from user where name="张三" and age=22
```
在完全可信的环境中,传递一个sql过来是没问题的,但绝大部份API环境我们都认为是危险的,让用户直接传一个sql语句实在是太【艺高人胆大了】,有什么办法可以优化呢?
有的,如果我们让用户只传name和age过来不就行了吗?好,现在的sql语句应该是:
```sql
select * from user where name="{{my_para}}" and age={{age}}
```
my_para和age就是在req阶段定义的输入参数,my_para是String,age是Number。
我们把上面的sql语句做为一个【合成的新变量字段】,假如传入的my_para=张三 ,age=22,那么生成的sql语句将是:
```sql
select * from user where name="张三" and age=22
```
看起来是没问题了。但里面有一个重大的安全隐患:sql注射! 因为用户输入的my_para可能会被注射,那如何解决呢,也简单,我们把变量{{my_para}}换成{{@my_para}}也就是加一个@符号,同时把变量外的双引号去掉,像下面这样:
```sql
select * from user where name={{@my_para}} and age={{@age}}
```
易源会对@符号引用的变量做sql注射的转义,因此它是安全的。由于my_para是String类型,因此
```sql
{{@my_para}} 将会生成 "张三"
```
而age是Number类型,因此
```sql
{{@age}} 将会生成 22
```
对应下来,整个sql语句生成后是:
```sql
select * from user where name="张三" and age=22
```
我们来看下图示操作:

## 3.2 使用场景2
通常在post提交时,使用的编码是:
```html
Content-Type: application/x-www-form-urlencoded;charset=utf-8
```
这也是表单在post时默认的提交编码:
```html
<form>
<input type="text" name="my_para">
</form>
```
但也有不少后端服务接收的不是application/x-www-form-urlencoded编码,而是json,例如:
```html
Content-Type: application/json
```
直观地讲,就是需要post一整个json串到接口,而不是key1=v&key2=v&key3=v这样的串。下面示例就是百度短链接接口的真实调用:
```bash
curl -H "Content-Type:application/json" -H "Token: 你的token" -X POST "https://dwz.cn/admin/v2/create" -d '{"url":"你的长网址","TermOfValidity ":"有效期"}'
```
可以看到post的编码是:
```html
Content-Type: application/json
```
而且post的body体为:
```json
{"url":"你的长网址","TermOfValidity ":"有效期"}
```
如果把百度的`https://dwz.cn/admin/v2/create`作为一个后端服务,我们当然可以要求客户端传输整个串`{"url":"你的长网址","TermOfValidity ":"有效期"}`到易源,易源再原封不动把它转发给百度就行,但是让客户端组织这个json串有时还是挺麻烦的,能不能让客户端只传url和TermOfValidity两个参数,然后易源把需要的json串给拼出来,再转发给百度不是更好?现在就来看看怎么做。
###首先定义url和TermOfValidity参数
就像这个图:

###在proxy中定义合成字段
我们需要合成的串是这样
```json
{"url":"你的长网址","TermOfValidity ":"有效期"}
```
刚才已经定义字段变量了,那现在可以做变量嵌入以生成上面的json串。
```json
{"url":{{@url}},"TermOfValidity ":{{@TermOfValidity}}}
```
把上面这串作为合成字段的模板,如图:

###在balance中定义后端服务
如下图所示:

这样我们就实现了从客户端输入 url和TermOfValidity字段,由易源组装json转发到百度,从而实现百度的短链接接口。
# 4. 代码转换
如果需要使用比字段映射更强的功能(比如复杂的逻辑判断),可以使用【代码转换】功能,嵌入LUA代码直接修改输入参数。本功能逻辑图如下:

您可以使用LUA代码,对输入参数进行任意的改动调整,您需要做的是实现以下的回调函数:
```lua
--如果返回非nil的字符串err_info,则流程中止,调用失败,系统对外返回备注为err_info.
function(
post, --post参数map
query, --query参数map
header, --header参数map
showapi_env,--全局环境变量
node_name --后端本次使用的节点名
)
--示例用法:在post设置num字段,然后删除query中的某个字段
post.num="15874521236"
query.num=nil
end
```
# 4.1 post,query,header
这三个参数比较简单明了,分别指代输入参数的三个位置,您可以在代码中对它们进行增加、修改、删除字段的处理。例如:
```lua
post.num="15874521236"
query.num=nil
--把post中的num设置一个新值,同时把query中的num字段移除。
```
> 如果在post容器中设置了showapi_bodyString参数,则整体请求体将被替换为showapi_bodyString
# 4.2 showapi_env
此输入参数是当前API主人定义的环境变量。如果有如下环境变量:

则在代码中可以使用:
```lua
local my_ip=showapi_env.my_ip
local my_port=showapi_env.my_port
--业务逻辑
```
# 4.3 node_name
在有多个后端节点的时候,易源会把选中的节点名称,传入lua回调函数中。这样代码可以根据不同的节点,进行不同的业务逻辑操作。例如下图就有node1和node2两个节点。

那么在lua回调时,node_name可能是`node1`和`node2`,您可以根据实际节点编写不同的代码逻辑。
# 4.4 回调函数返回
如果本回调返回nil,则流程继续; 如果返回一个字符串(比如说"业务不允许这样的输入"),则整个请求流程终止,易源将整体返回类似如下信息:
```json
{
"showapi_res_error": "lua code transform err: 业务不允许这样的输入",
"showapi_res_id": "1ba8f18564e44ceab46f062fffb4e2f2",
"showapi_res_code": -1005,
"showapi_res_body": { }
}
```
# 4.5 使用实例介绍
比如我们要连接手机归属地查询接口,有两个API源(比如阿里市场上两个服务商提供的【手机归属地】接口),它们的输入参数分别是num和number,而我们的接口定义的输入参数是phone,如图所示:

那我们可以在易源的proxy阶段编写代码转换,根据结点名称进行不同的操作,如下图流程:

此lua代码像下面的样子:
```lua
function(
post, --post参数map
query, --query参数map
header, --header参数map
showapi_env,--全局环境变量
node_name --后端本次使用的节点名
)
if node_name=="node1" then --对node1的映射
post.num=post.phone
elseif node_name=="node2" then --对node2的映射
post.number=post.phone
end
post.phone=nil --移除原先的phone字段
end
```
> 上述代码是假设传入的方式是post。如果是get,则代码体里需要操作query。