apigateway-kong(三)Proxy规则

时间:2024-01-20 20:05:08

本篇详细记录了Kong的代理功能及其路由功能和内部工作。

  Kong公开了几个可以通过两个配置属性进行调整的接口:
proxy_listen,默认8000,它定义Kong将接受来自客户端的公共流量并将其代理到您的上游服务的地址/端口列表。
admin_listen,默认8001,它还定义了一个地址和端口列表,但这些列表应该仅限于管理员访问,因为它们揭示了Kong的配置功能:Admin API

注意:从kong v0.13.0开始,API实体已被弃用。本文档将介绍新的路由和服务实体的代理。

一些术语

client:指下游客户向Kong的代理端口发出请求。
upstream service:指自己的API /服务,位于Kong后面,客户请求被转发到该服务。
service:服务实体,顾名思义,是对自己的每个上游服务的抽象。服务的例子可以是数据转换微服务,账单API等。
route:这是指Kong Routes实体。路由是进入Kong的入口点,并为要匹配的请求定义规则,并路由到给定的Service。
plugin:这是指Kong的“插件”,它是在代理生命周期中运行的业务逻辑。可以通过ADMIN API配置插件 - 全局(所有传入流量)或特定的路由和服务。

概览

  从上层角度看,Kong在其配置的代理端口(默认情况下为8000和8443)上侦听HTTP流量。 Kong会根据你配置的路由评估任何传入的HTTP请求,并尝试找到匹配的路由。如果某个请求符合特定路由的规则,Kong将处理代理请求。由于每条路由都与一个服务链接,因此Kong将运行您在路由及其相关服务上配置的插件,然后向上游代理请求。
您可以通过Kong的Admin API管理routes,路由的hosts,paths和methods属性定义匹配传入HTTP请求的规则。
如果Kong收到无法匹配任何已配置路由的请求(或者没有配置路由),则它将以下列情况作出响应:

HTTP/1.1 404 Not Found
Content-Type: application/json
Server: kong/<x.x.x>

{
    "message": "no route and no API found with those values"
}

注意:这个消息提到了“API”,因为为了向后兼容的原因,Kong 0.13仍然支持API实体(并且如果没有首先匹配任何路由,则尝试匹配针对任何配置的API的请求)。

Kong是一个透明的代理,默认情况下它会将请求转发给上游服务,但HTTP规范要求的各种头文件(例如Connection,Date和其他头文件)除外 

关于如何通过配置Route和Service来代理到一个上游服务,参考上一篇admin-api配置示例说明

路由匹配规则

  现在看下Kong如何将请求与路由的配置hosts, paths and methods属性(或字段)进行匹配,注意:这三个字段都是可选的,但至少必须指定其中的一个

匹配一个route的请求:

  • 该请求必须包含所有配置的字段
  • 请求中字段的值必须至少匹配一个配置的值(虽然字段配置接受一个或多个值,但请求只需要其中一个值被视为匹配)

例子:如下配置的route如何匹配:

{
    "hosts": ["example.com", "foo-service.com"],
    "paths": ["/foo", "/bar"],
    "methods": ["GET"]
}

下面是与此路由匹配的一些请求:

GET /foo HTTP/1.1
Host: example.com
GET /bar HTTP/1.1
Host: foo-service.com
GET /foo/hello/world HTTP/1.1
Host: example.com


这三个请求都满足路径定义中设置的所有条件但是,以下请求不符合配置的条件:

# 未指定paths
GET / HTTP/1.1
Host: example.com
# method不匹配
POST /foo HTTP/1.1
Host: example.com
# request host header不匹配
GET /foo HTTP/1.1
Host: foo.com

请求主机头-Request Host header

  根据Host头来路由请求是通过Kong代理流量的最直接的方式,这是HTTP Host头的预期用法。 Kong通过
主机接受多个值,当通过管理API指定时,它们必须以逗号分隔。

主机接受多个值,这在JSON payload中很简单:

$ curl -i -X POST http://localhost:8001/routes/ \
    -H 'Content-Type: application/json' \
    -d '{"hosts":["example.com", "foo-service.com"]}'
HTTP/1.1 201 Created
...

但由于Admin API也支持form-urlencoded content types,所以可以通过[]表示法指定一个数组:

$ curl -i -X POST http://localhost:8001/routes/ \
    -d 'hosts[]=example.com' \
    -d 'hosts[]=foo-service.com'
HTTP/1.1 201 Created
...

为了满足此route的hosts条件,来自客户端的任何传入请求现在必须将其主机头设置为以下之一:

Host: example.com
或
Host: foo-service.com

使用通配符主机名

  为了提供灵活性,Kong允许在hosts字段中指定带有通配符的主机名。通配符主机名允许任何匹配的主机头部满足条件,从而匹配给定的路由。通配符主机名在domain的最左侧或最右侧标签中只能包含一个*
例如:
* .example.com将允许主机值(如a.example.com和x.y.example.com)匹配。
example.*将允许诸如example.com和example.org的主机值匹配。

 

例如路由中指定如下hosts:

{
    "hosts": ["*.example.com", "service.com"]
}

将允许以下请求匹配此route:

GET / HTTP/1.1
Host: an.example.com
GET / HTTP/1.1
Host: service.com

preserve_host属性

  代理时,Kong的默认行为是将上游请求的主机头设置为Service主机中指定的主机名。 preserve_host字段接受一个布尔标志,默认false,指示Kong不这样做。

例如,如果preserve_host属性未更改且route配置如下:

{
    "hosts": ["service.com"],
    "service": {
        "id": "..."
    }
}

客户端可能向kong发出的一个请求:

GET / HTTP/1.1
Host: service.com

Kong会从Service's host属性中提取主机头值,并发送以下上游请求:

GET / HTTP/1.1
Host: <my-service-host.com>

但是,通过使用preserve_host = true明确配置路由:

{
    "hosts": ["service.com"],
    "preserve_host": true,
    "service": {
        "id": "..."
    }
}

假设来自客户端的相同请求:

GET / HTTP/1.1
Host: service.com

Kong将保留客户端请求的主机,并将发送以下上游请求:

GET / HTTP/1.1
Host: service.com

请求路径-Request path

  路由匹配的另一种方式是通过请求路径,为了满足此路由条件,客户端请求的路径必须以路径属性值之一作为前缀。
例如,使用如下配置的Route:

{
    "paths": ["/service", "/hello/world"]
}

下面这些请求将会匹配:

GET /service HTTP/1.1
Host: example.com
GET /service/resource?param=value HTTP/1.1
Host: example.com
GET /hello/world/resource HTTP/1.1
Host: anything.com

对于这些请求中的每一个,Kong都会检测到它们的URL路径前缀有一个路由路径值。默认情况下,Kong会在不改变URL路径的情况下向上游代理请求。
使用路径前缀进行代理时,首先会评估最长的路径。这使您可以定义两条路径:/service 和 /service/resource。

在path中使用正则表达式

  Kong通过PCRE(Perl Compatible Regular Expression)支持路径字段的正则表达式模式匹配。

可以同时将路径作为前缀和正则表达式分配给路由,如下面的路由配置:

{
    "paths": ["/users/\d+/profile", "/following"]
}

下面这些请求将会匹配这条路由:

GET /following HTTP/1.1
Host: ...
GET /users/123/profile HTTP/1.1
Host: ...

使用PCRE标志(PCRE_ANCHORED)评估提供的正则表达式,这意味着它们将被限制为匹配路径中的第一个匹配点(根/字符)

评估(匹配)顺序

  如前所述,Kong根据长度评估前缀路径:首先评估最长的前缀路径。但是,Kong将根据路由的regex_priority属性评估正则表达式路径。

这意味着考虑以下路线:

[
    {
        "paths": ["/status/\d+"],
        "regex_priority": 0
    },
    {
        "paths": ["/version/\d+/status/\d+"],
        "regex_priority": 6
    },
    {
        "paths": ["/version"],
        "regex_priority": 3
    },
]

在这种情况下,Kong将按照以下顺序针对以下定义的URI评估传入请求:

  1. /version
  2. /version/\d+/status/\d+
  3. /status/\d+

前缀路径总是先评估。

像往常一样,请求必须仍然与路由的主机和方法属性相匹配,并且Kong将遍历您的路由,直到找到与最多规则匹配的路由

捕获组

  支持捕获组,并且匹配的组将从路径中提取并可供插件使用。如果我们考虑以下正则表达式:

/version/(?<version>\d+)/users/(?<user>\S+)

请求路径如下:

/version/1/users/john

Kong将认为该请求路径匹配,并且如果总体路由匹配(考虑hosts和methods字段),解压缩的捕获组将可以从ngx.ctx变量的插件中获得:

local router_matches = ngx.ctx.router_matches

-- router_matches.uri_captures is:
-- { "1", "john", version = "1", user = "john" }

转义特殊字符

  接下来,值得注意的是,在正则表达式中找到的字符通常是根据RFC 3986的保留字符,因此应该使用percent-encoded。通过管理API配置正则表达式路径时,请确保在必要时对您的payload进行URL编码。例如,使用curl并使用application/x-www-form-urlencoded MIME 类型:

$ curl -i -X POST http://localhost:8001/routes \
    --data-urlencode 'uris[]=/status/\d+'
HTTP/1.1 201 Created
...

注意,curl不会自动对payload进行URL编码,并且请注意--data-urlencode的用法,该操作可防止+字符被URL解码,并将其解释为Kong's Admin API的空间。

strip_path属性

  可能需要指定一个path前缀来匹配Route,但不会将其包含在上游请求中。为此,请通过配置Route来使用strip_path布尔属性,如下所示:

{
    "paths": ["/service"],
    "strip_path": true,
    "service": {
        "id": "..."
    }
}

启用此标志将指示Kong在匹配此路由并继续处理代理服务时,不应在上游请求的URL中包含匹配的URL路径部分

例如,以下客户端对上述路由的请求:

GET /service/path/to/resource HTTP/1.1
Host: ...

将导致Kong发送以下上游请求:

GET /path/to/resource HTTP/1.1
Host: ...


同样,如果在启用strip_path的Route上定义了正则表达式路径,则请求URL匹配序列的全部内容将被删除。

例:

{
    "paths": ["/version/\d+/service"],
    "strip_path": true,
    "service": {
        "id": "..."
    }
}

以下HTTP请求与提供的正则表达式路径匹配:

GET /version/1/service/path/to/resource HTTP/1.1
Host: ...

kong将上游代理为:

GET /path/to/resource HTTP/1.1
Host: ...


请求HTTP方法-Request HTTP method

  方法字段允许根据HTTP方法匹配请求。它接受多个值。它的默认值是空(HTTP方法不用于路由)。

下面端路由允许通过GET/HEAD方法进行路由:

{
    "methods": ["GET", "HEAD"],
    "service": {
        "id": "..."
    }
}

下面的请求匹配上述路由:

GET / HTTP/1.1
Host: ...
HEAD /resource HTTP/1.1
Host: ...

上述路由不匹配POST和DELETE请求。

在路由上配置插件时,这可以实现更细的粒度。例如,可以设想两个路由指向相同的服务:一个具有无限的未经身份验证的GET请求,另一个只允许经过身份验证和速率限制的POST请求(通过将身份验证和速率限制插件应用于此类请求)。