标签归档:Openresty

基于OpenResty 的 WEB 框架 Lor 安装初探

  • 项目介绍:
    • Lor是一个运行在OpenResty上的基于Lua编写的Web框架.
    • 路由采用Sinatra风格,Sinatra是Ruby小而精的web框架.
    • API基本采用了Express的思路和设计,Node.js跨界开发者可以很快上手.
    • 支持插件(middleware),路由可分组,路由匹配支持string/正则模式.
    • lor以后会保持核心足够精简,扩展功能依赖middleware来实现. lor本身也是基于middleware构建的.
    • 推荐使用lor作为HTTP API Server,lor也已支持session/cookie/html template等功能.
    • 框架简单示例项目lor-example
    • 框架全站示例项目openresty-china
  • 项目地址: https://github.com/sumory/lor
  • 文档地址: http://lor.sumory.com

安装lor框架

git clone https://github.com/sumory/lor
cd lor 
make install

此时可能会有报错如下:

/usr/bin/env: "resty": 没有那个文件或目录

原因为lor没有找到resty的执行目录,这个时候只需要找到resty的执行目录并软链过去即可:

sudo ln -s /usr/local/openresty/bin/resty /usr/bin/resty

框架自带一个示例项目,运行一下代码

lord new lor_demo

启动

然后就能在框架中看到lor_demo文件夹,执行如下命令

cd lor_demo
lord start

打开浏览器,输入http://localhost:8888/正常访问,即正常安装。

使用 konga 来管理微服务 API 网关 kong

微服务网关kong有比较多个后台管理面板,比如比较简单的kong-dashboard,还有konga,之前在初探kong的时候,使用的就是比较简单的kong-dashboard,很多功能都没有,而且最近由于kong官方更新比较频繁,1.0之后的kong-dashboard就已经不兼容了,频繁报错,所有今天我就来使用一下另一款kong的后台管理面板:konga

安装konga

在开始安装之前,需要准备的有:

  • 一个已经安装好的kong环境
  • Nodejs >= 8 (推荐8.11.3 LTS)
  • npm

关于这三者的安装,我这里就不赘述了,kong的安装可以在博客的相关文章查看。在做好准备工作之后,就开始安装:

安装npm依赖

$ git clone https://github.com/pantsel/konga.git
$ cd konga
$ npm i

在执行npm i之后,由于会引入很多的npm包,所以可能会有报错,比如我这里就遇到了一些权限问题,比如:

Unable to save binary /home/qianyugang/soft/konga/node_modules/node-sass/vendor/linux-x64-64 : { Error: EACCES: permission denied, mkdir '/home/thinkpad/soft/konga/node_modules/node-sass/vendor'

如果出现了如上错误,可以把最后一个命令修改为

sudo npm i --unsafe-perm=true --allow-root

执行之后,可能会提示:

bower bootstrap-switch extra-resolution Unnecessary resolution: bootstrap-switch#~3.3.4
added 69 packages from 77 contributors and audited 3120 packages in 4.654s
found 275 vulnerabilities (125 low, 24 moderate, 126 high)
  run `npm audit fix` to fix them, or `npm audit` for details

意思是有一些包没有执行好,那么我们就按照它的提示执行一下:

sudo npm audit fix --unsafe-perm=true --allow-root

基本就可以解决npm依赖包的问题了。总之这里是有可能出现各种各样的npm问题,依次解决即可。

初始化数据库

npm的依赖都安装完成之后,就需要复制一下konga目录下的.env_example文件

cp .env_example env

然后把其中的一些配置项目都填写上去。具体的配置项可以查看 https://github.com/pantsel/konga#environment-variables 。这里就主要把以下三项配置好:

DB_ADAPTER=postgres
PORT=1337
DB_URI=postgresql://kong_user:kong_pass@localhost:5432/konga

注意这个DB_URI一定要填写完整,这里的数据库我用的是postgres,konga其实还支持mysql,mongo等多种数据库,这里就不赘述了。配置完成之后,需要登上你自己的postgres数据库,然后执行如下命令新建数据库:

CREATE DATABASE "konga" WITH ENCODING='UTF8';

新建了数据库之后,,执行如下命令,来初始化数据库:

node ./bin/konga.js prepare --adapter postgres --uri postgresql://kong_user:kong_pass@localhost:5432/konga

出现如下提示之后,数据库这一块就完成:

Preparing database...
debug: Hook:api_health_checks:process() called
debug: Hook:health_checks:process() called
debug: Hook:start-scheduled-snapshots:process() called
debug: Hook:upstream_health_checks:process() called
debug: Hook:user_events_hook:process() called
debug: Seeding User...
debug: User seed planted
debug: Seeding Kongnode...
debug: Kongnode seed planted
debug: Seeding Emailtransport...
debug: Emailtransport seed planted
debug: Database migrations completed!

启动konga

执行如下命令:

npm start

看到如下小帆船的图,成功启动


   Sails              <|    .-..-.
   v0.12.14            |\
                      /|.\
                     / || \
                   ,'  |'  \
                .-'.-==|/_--'
                `--'-------' 
   __---___--___---___--___---___--___
 ____---___--___---___--___---___--___-__

最后,打开浏览器输入http://localhost:1337/,就可以进入konga的管理界面了。会出现一个让你注册的界面,注册登录一下,然后配置一下kong的admin-api链接,大功告成,打完收工。

微服务 API 网关 Kong 中文文档发布

由于项目的原因,最近的几个月一直在学习微服务的API网关 Kong ,在这里做一个简单介绍,是一个云原生,高效,可扩展的分布式 API 网关。 自 2015 年在 github 开源后,广泛受到关注,目前已收获 1.68w+ 的 star,其核心价值在于高性能和可扩展性。由于对项目的积极维护,Kong被广泛用于从初创公司到全球5000强以及政府机构的生产中。从技术角度来说,Kong是基于Openresty的一个莹莹,Openresty是基于Nginx的,使用的语言是Lua。

所以在学习过程中,首要是需要看官方文档,由于项目比较新,所以暂时没有中文文档,我就想着,反正文档总是要全部看一遍的,不如自己翻译一份好了,于是乎就有了这个项目:Kong的文档中文版。欢迎大家star&fork。

由于自己不是英语专业,而且主要目的是学习Kong,所以采用的是人工+机翻结合的方式,如果有遇到翻译的不够通顺,或者对于翻译的语句有歧义的地方,麻烦一定点击官网英文文档https://docs.konghq.com/ 查看,并且欢迎提 PR 提修改意见。另,由于kong的文档本身也在不断增加和完善当中,如果有遇到没有即使更新翻译的状况欢迎提issue,我会不断补充的。

todo:

  • 目前文档中的超链接都是链接的英文原文,后续会慢慢改成中文内链。
  • 会在每一页文档里面附上单独的英文原文链接,以便做对照。
  • 会添加kong自带的插件文档。

本文档是基于 https://docs.konghq.com/1.1.x/ 1.1.x 版本,目前官网已经更新至 1.2.x 版本,如果使用的最新版本,请查看 https://docs.konghq.com 并注意差别。

Lua OpenResty 使用 protobuf 和 rabbitmq AMQP 发送和接收消息

项目中有个需求,需要使用Google的protobuf作为压缩协议,然后使用rabbitmq AMQP来发送和接收消息,在研究使用这两个工具中,遇到了有一些坑,之前有写了两篇来介绍分别使用,《 在 lua 中使用 protobuf》和《Lua OpenResty 使用rabbitmq AMQP协议发送和接收消息》 ,这里我们来结合使用一下,然后顺便解决一下lua的相关库的使用问题。

protobuf 相关步骤

1.安装 protoc

首先安装依赖库

sudo apt-get install autoconf automake libtool curl make g++ unzip

下载、解压安装包

curl -L -o protobuf-all-3.6.1.tar.gz  https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-all-3.6.1.tar.gz
tar -xzvf protobuf-all-3.6.1.tar.gz

进入安装包安装

cd protobuf-all-3.6.1
./configure
make
make check
sudo make install
sudo ldconfig

最后检查是否安装成功

$ protoc --version
libprotoc 3.6.1

2.生成pb文件

需要定义你自己的标准proto文件 xxx.proto 文件(可能不止一个文件,不止一个目录),定义好了之后,使用protoc命令生成.pb文件,命令如下:

protoc --proto_path=proto --descriptor_set_out=common.pb proto/xxxx/*.proto proto/xxxxx/*.proto

其中 --proto_path 参数是你的proto文件目录,--descriptor_set_out 是你需要输出.pb文件的目录,后面几个参数就是具体要引入的.proto文件。这里需要注意的是,有时候会有多个proto文件并且多目录import的情况,这个时候,就需要在参数中都体现出来(命令的最后两个参数),这条命令是把所有的 .proto 文件生成了一个 common.pb 文件方便引入。注意这个common.pb的路径,后面需要用到。

3.安装lua的protobuf库

这里我们使用的是lua-protobuf库。 lua-protobuf实际上是一个纯C的protobuf协议实现,和对应的Lua绑定。

项目地址:https://github.com/starwing/lua-protobuf

可以使用 luarocks 安装lua-protobuf

luarocks install lua-protobuf

如果没有安装 luarocks 可以安装一下luarocks。

rabbitmq AMQP 相关步骤

1.安装第三方库

如果想要在openresty中使用AMQP协议发送和接收消息的话,需要使用到一个第三方库。地址为:https://github.com/4mig4/lua-amqp 。这里你会发现,这个库和《Lua OpenResty 使用rabbitmq AMQP协议发送和接收消息》文章中介绍使用的库不一样,确实是的,因为在使用过程中我发现,之前的那个库还有一些功能不完善,这里使用的这个库是作者fork了之前的那个项目,并改进了里面许多功能。所以最终我选用了这个库来开发。

安装方式为,使用luarocks安装,由于这个库没有push到https://luarocks.org/ 仓库当中,所以我们需要使用到其中的声明文件安装。把项目clone下来之后,找到这个文件 amqp-1.0-4.rockspec 这个文件,然后执行:

luarocks build amqp-1.0-4.rockspec

这里直接执行,可能会报错,报错信息为unrecognized filename extension,意思是识别不了文件格式之类的,这里需要把这个文件中的

source = {
   url = "https://github.com/4mig4/lua-amqp.git",
   tag = "",
}

修改为:

source = {
   url = "https://github.com/4mig4/lua-amqp",
   tag = "",
}

然后继续执行,执行完成之后,就可以在/usr/local/share/lua/5.1目录中看到这个第三方库了,如果整理的文件夹名字不是amqp,而是一个amqp加版本号,可以手动直接把文件夹修改为amqp即可。到此第三方库安装完成。

在使用过程中,我发现一个问题,作者在fork了项目之后,增加了CQUEUES, NGX.SOCKET, SOCKET一些通讯协议功能,但是我本地环境直接运行会报错,查看了一些源码,发现是我的本地环境的cqueues支持有点问题,而作者把这个作为最优先的协议,那么我就把源码文件https://github.com/4mig4/lua-amqp/blob/master/amqp/init.lua 中的local use_cqueues = true改为local use_cqueues = false,即关闭cqueues来使用了。

发送和接收消息

假设你的proto结构体是这样的:

syntax = "proto3";
package aa.bb;
message EventEnvelope {
  string id = 1; 
  int64 created_ts = 2; 
  string server_hash = 3;
  int64 happened_ts = 4; 
  oneof body {
    aa.bb.LoggedOut logged_out = 1501;
  }
message LoggedOut {
  int64 union_id = 1;
  string app_key = 2;
}

发送消息

local function send_pb()

    -- 引入pb库
    local pb   = require "pb"
    -- 加载pb文件
    assert(pb.loadfile("xxxxx/common.pb"))

    -- 这里是你需要发送的pb消息,注意这里是支持嵌套的,如果你的protobuf文件里面有one of,可以直接使用多层嵌套
    -- 这里就根据你自己protobuf结构来就好
    local data    = {
        created_ts  = ngx.now() * 1000,
        server_hash = 'localhost',
        happened_ts = ngx.now() * 1000,
        id          = 8376548368364,
        logged_out  = {
            union_id = 123455666,
            app_key  = xxxxxxxx
        }
    }

    -- 生成需要发送的消息,这个消息是proto压缩之后的,这个`EventEnvelope` 就是最外层的结构体
    local messages = assert(pb.encode("aa.bb.EventEnvelope", data))

    -- 引入amqp第三方库
    local amqp        = require "amqp"

    -- 里面的一些参数就不再赘述了,都是rabbitmq的一些参数
    local ctx = amqp.new({
        role        = "publisher",
        exchange    = "exchangexxxx",
        ssl         = false,
        user        = "guest",
        password    = "guest",
        auto_delete = false,
        routing_key = "routing_keyxxxxx",
        passive     = true,
        no_ack      = true,
        no_wait     = false,
    })
    ctx:connect("127.0.0.1", port)
    ctx:setup()
    local ok, err = ctx:publish(messages)
    if not ok then
        ngx.log(ngx.ERR, "[ -- rabbitmq send failed : -- ] " .. err)
    else
        ngx.log(ngx.ERR, "[ -- rabbitmq send success ]")
    end
end

send_pb("this is a message")

接收消息

可以新建一个consume_queue.lua文件,然后如下代码:


-- 依旧是引入
local amqp            = require "amqp"
local pb              = require "pb"

-- 这里是回调函数
local function consume_local(f)
    print(f) -- 这里就是消息的所有信息,里面包含了properties ,body,frame等信息 
    print(f.body) -- 这里就是消息的主体

       -- 加载pb文件
    assert(pb.loadfile("xxxxx/common.pb"))
    -- 解析消息
    local data = assert(pb.decode("aa.bb.EventEnvelope", f.body))
    print(data) 

end

local amqp        = require "amqp"

local ctx = amqp.new({
    role        = "consumer",
    queue       = "eventbus1", -- 这里可以自定义
    exchange    = "exchangexxxx",
    ssl         = false,
    user        = "guest",
    password    = "guest",
    no_wait     = false,
    routing_key = "routing_keyxxxxx",
    auto_delete = false, -- 是否自动删除消息
    no_ack      = true,
    exclusive   = false, -- 是否为排他队列
    callback    = consume_local, -- 回调函数
    durable     = true,
    passive     = false,
    type        = "topic"

})

ctx:connect("127.0.0.1", port)

local ok, err = ctx:consume()

执行命令开始消费消息

/usr/local/openresty/bin/resty consume_queue.lua

微服务 API 网关 Kong 日志中文文档

日志

原文链接: https://docs.konghq.com/1.1.x/logging/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

日志等级

日志级别在Kong的配置中设置。以下是日志级别,按照严重程度顺序递增,debug, info, notice, warn, error and crit

  • debug:它提供有关插件的runloop和每个插件或其他组件的调试信息。只是在调试期间使用,因为它的消息量太多了。
  • info/notice:kong没有在这两个级别上产生很大的差异。提供有关正常行为的信息,其中大多数行为可以忽略。
  • warn:要记录任何不会导致事务丢失但需要进一步调查的异常行为,应使用警告级别。
  • error:用于记录导致请求被停止的错误(例如,获取HTTP 500错误)。需要监控此类日志的速率。
  • crit:当Kong在紧急条件下工作而不能正常工作从而影响多个客户时,使用此级别。

默认情况下,notice是使用和建议的日志级别。然而,如果日志变得过于繁琐,他们可能会被提升到更高的水平,就像warn一样。

从Kong日志中删除某些元素

随着围绕保护私人数据(如GDPR)的新规定,您可能需要改变您的日志记录习惯。如果您使用Kong作为API网关,则可以在一个位置完成此操作以使所有API生效。本指南将引导您完成一个实现此目的的方法,但总有不同的方法来满足不同的需求。请注意,这些更改将影响NGINX访问日志的输出。这对Kong的日志插件没有任何影响。

举个例子,假设您要从kong日志中删除任何电子邮件地址实例。电子邮件地址可能以不同的方式出现,例如/apiname/v2/verify/alice@example.com 或者 /v3/verify?alice@example.com。为了防止这些被添加到日志中,我们需要使用自定义NGINX模板。

要开始使用自定义NGINX模板,请先获取我们模板的副本。 这可以在 https://docs.konghq.com/latest/configuration/#custom-nginx-templates-embedding-kong 找到或从下面复制

# ---------------------
# custom_nginx.template
# ---------------------

worker_processes $; # can be set by kong.conf
daemon $;                     # can be set by kong.conf

pid pids/nginx.pid;                      # this setting is mandatory
error_log logs/error.log $; # can be set by kong.conf

events {
    use epoll; # custom setting
    multi_accept on;
}

http {
    # include default Kong Nginx config
    include 'nginx-kong.conf';

    # custom server
    server {
        listen 8888;
        server_name custom_server;

        location / {
          ... # etc
        }
    }
}

为了控制日志中的内容,我们将在模板中使用NGINX 的map模块。有关使用map指令的更多详细信息,请参阅本指南。这将创建一个新变量,其值取决于第一个参数中指定的一个或多个源变量的值。格式为:

map $paramater_to_look_at $variable_name {
    pattern_to_look_for 0;
    second_pattern_to_look_for 0;

    default 1;
}

举个例子,我们将映射一个名为keeplog的新变量,该变量依赖于$request_uri中出现的某些值。我们将把map指令放在http块的开头,这必须在 include'nginx-kong.conf' 之前。因此,对于我们的示例,我们将添加以下内容:

map $request_uri $keeplog {
    ~.+\@.+\..+ 0;
    ~/servicename/v2/verify 0;
    ~/v3/verify 0;

    default 1;
}

您可能会注意到这些行中的每一行都以波形符号开头。这就是NGINX在评估生产线时使用RegEx的原因。 在这个例子中我们有三件事需要寻找:

  • 第一行使用正则表达式查找x@y.z格式的任何电子邮件地址
  • 第二行查找URI的任何部分,即/servicename/v2/verify
  • 第三行查看包含/v3/verify的URI的任何部分

因为所有这些都具有0以外的值,如果请求具有其中一个元素,则不会将其添加到日志中。

现在,我们需要为日志中保留的内容设置日志格式。我们将使用log_format模块并为我们的新日志指定show_everything的名称。日志的内容可以根据您的需要进行定制,但在这个例子中,我会简单地将一切改回kong标准,要查看可以使用的完整选项列表,请参阅本指南

log_format show_everything '$remote_addr - $remote_user [$time_local] '
    '$request_uri $status $body_bytes_sent '
    '"$http_referer" "$http_user_agent"';

现在,我们的自定义NGINX模板已经可以使用了。如果您一直观察,您的文件现在应该如下所示:

# ---------------------
# custom_nginx.template
# ---------------------

worker_processes $; # can be set by kong.conf
daemon $;                     # can be set by kong.conf

pid pids/nginx.pid;                      # this setting is mandatory
error_log stderr $; # can be set by kong.conf



events {
    use epoll; # custom setting
    multi_accept on;
}

http {


    map $request_uri $keeplog {
        ~.+\@.+\..+ 0;
        ~/v1/invitation/ 0;
        ~/reset/v1/customer/password/token 0;
        ~/v2/verify 0;

        default 1;
    }
    log_format show_everything '$remote_addr - $remote_user [$time_local] '
        '$request_uri $status $body_bytes_sent '
        '"$http_referer" "$http_user_agent"';

    include 'nginx-kong.conf';
}

我们需要做的最后一件事是告诉Kong使用新创建的日志,show_everything,为此,我们将改变Kong变量prpxy_access_log。通过打开和编辑etc/kong/kong.conf或使用环境变量KONG_PROXY_ACCESS_LOG=来修改默认位置以显示。

proxy_access_log=logs/access.log show_everything if=$keeplog

最后一步,重启kong,使修改东西都生效,你可以使用kong restart命令来操作。

现在,将不再记录使用其中的电子邮件地址发出的任何请求。 当然,我们可以使用此逻辑以条件方式从日志中删除任何我们想要的内容。

微服务 API 网关 Kong 代理中文文档

原文链接: https://docs.konghq.com/1.1.x/proxy/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

代理

简介

在本文档中,我们将通过详细解释其路由功能和内部工作原理,来了解Kong的代理功能。

Kong公开了几个可以通过两个配置属性调整的接口:

  • proxy_listen:它定义了一个地址/端口列表,Kong将接受来自客户端的公共流量并将其代理到您的上游服务(默认为8000)。
  • admin_listen:它还定义了一个地址和端口列表,但是这些应该被限制为仅由管理员访问,因为它们暴露了Kong的配置功能:Admin API(默认为8001)。

注意:从1.0.0开始,API实体已被删除。本文档将介绍使用新路由和服务实体进行代理。
如果您使用的是0.12或更低版本,请参阅本文档的旧版本。

相关术语

  • client:指下游 downstream 客户向Kong的代理端口发出请求。
  • upstream service:指位于Kong后面的您自己的 API/service ,转发客户端请求。
  • Service:顾名思义,服务实体是每个上游服务的抽象。比如说服务可以是数据转换微服务,一个计费API等。
  • Route:这是指Kong Routes实体。路由是进入Kong的入口点,并定义要匹配的请求的规则,并路由到给定的服务。
  • Plugin:这指的是Kong“插件”,它们是在代理生命周期中运行的业务逻辑。可以通过Admin API配置插件 – 全局(所有传入流量)或特定路由和服务。

概览

从高层次的角度来看,Kong在其配置的代理端口上监听HTTP流量(默认情况下为80008443)。Kong将根据您配置的路由评估任何传入的HTTP请求,并尝试查找匹配的路由。如果给定的请求与特定Route的规则匹配,Kong将处理代理请求。由于每个Route都链接到一个Service,因此Kong将运行您在Route及其相关服务上配置的插件,然后在上游代理请求。

您可以通过Kong的Admin API管理Routes。Routes 的机 hostspsths methods 属性定义用于匹配传入HTTP请求的规则。

如果Kong收到的请求无法与任何已配置的路由匹配(或者如果没有配置路由),它将响应:

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

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

提醒:如何配置服务

配置服务快速入门指南介绍了如何通过[Admin API]配置Kong。

通过向Admin API发送HTTP请求来向Kong添加服务:

curl -i -X POST http://localhost:8001/services/ \
    -d 'name=foo-service' \
    -d 'url=http://foo-service.com'
HTTP/1.1 201 Created
...

{
    "connect_timeout": 60000,
    "created_at": 1515537771,
    "host": "foo-service.com",
    "id": "d54da06c-d69f-4910-8896-915c63c270cd",
    "name": "foo-service",
    "path": "/",
    "port": 80,
    "protocol": "http",
    "read_timeout": 60000,
    "retries": 5,
    "updated_at": 1515537771,
    "write_timeout": 60000
}

该请求指示Kong注册一个名为“foo-service”的服务,该服务指向http://foo-service.com(上游)。

注意:url参数是一个简化参数,用于一次性添加protocolhostportpath

现在,为了通过Kong向这个服务发送流量,我们需要指定一个Route,它作为Kong的入口点:

curl -i -X POST http://localhost:8001/routes/ \
    -d 'hosts[]=example.com' \
    -d 'paths[]=/foo' \
    -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'
HTTP/1.1 201 Created
...

{
    "created_at": 1515539858,
    "hosts": [
        "example.com"
    ],
    "id": "ee794195-6783-4056-a5cc-a7e0fde88c81",
    "methods": null,
    "paths": [
        "/foo"
    ],
    "preserve_host": false,
    "priority": 0,
    "protocols": [
        "http",
        "https"
    ],
    "service": {
        "id": "d54da06c-d69f-4910-8896-915c63c270cd"
    },
    "strip_path": true,
    "updated_at": 1515539858
}

我们现在已经配置了一个Route来匹配与给定hostpath匹配的传入请求,并将它们转发到我们配置的foo-service,从而将此流量代理到http://foo-service.com

路由和匹配功能

现在让我们讨论Kong如何匹配针对路由的已配置hostpathmethods属性(或字段)的请求。请注意,所有这三个字段都是可选的,但必须至少指定其中一个。

对于匹配路线的请求:

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

我们来看几个例子。考虑如下配置的路由:

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

与此Route匹配的一些可能请求如下所示:

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

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

GET / HTTP/1.1
Host: example.com
POST /foo HTTP/1.1
Host: example.com
GET /foo HTTP/1.1
Host: foo.com

所有这三个请求仅满足两个配置条件。第一个请求的路径不匹配配置的路径,第二个请求的HTTP方法和第三个请求的host头也均不匹配。

现在我们了解了hosts, paths, 和 methods属性如何协同工作,让我们分别来看每个属性。

host 请求头

基于其host header 来路由请求是通过Kong代理流量的最直接方式,特别是因为这是HTTP host header 的预期用途。Kong可以通过Route实体的hosts字段轻松完成。

hosts接受多个值,在通过Admin API指定它们时必须以逗号分隔:

hosts接受多个值,这些值很容易在JSON有效负载中表示:

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内容类型,因此您可以通过[]表示法指定数组:

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 header 设置为以下之一:

Host: example.com

或者

Host: foo-service.com

使用通配符主机名

为了提供灵活性,Kong允许您在hosts字段中指定带通配符的主机名。通配符主机名允许任何匹配的host满足条件,从而匹配给定的Route。

通配符主机名必须在域的最左侧或最右侧标签中仅包含一个星号。例子:

  • *.example.com将匹配诸如a.example.com  x.y.example.com
  • example.* 将匹配诸如example.com  example.org

一个完整的例子如下所示:

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

将允许以下请求匹配此路由:

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

preserve_host属性

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

例如,当preserve_host属性未更改且Route配置如下:

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

client对Kong的可能请求可能是:

GET / HTTP/1.1
Host: service.com

Kong将从Service的主机属性中提取Host头值,并将发送以下上游请求:

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

但是,通过使用preserve_host=true配置Route:

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

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

GET / HTTP/1.1
Host: service.com

Kong将根据客户端请求保留Host,并将发送以下上游请求:

GET / HTTP/1.1
Host: service.com

请求路径

路由匹配的另一种方式是通过请求路径。 要满足此路由条件,客户端请求的路径必须以paths属性的值之一为前缀。 例如,使用如下配置的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路径以路由的paths之一为前缀。默认情况下,Kong会在不更改URL路径的情况下代理上游请求。

使用路径前缀进行代理时,首先评估最长路径。这允许您定义两个具有两个路径的Routes:/service/service/resource,并确保前者不会“遮蔽”后者。

在路径中使用正则表达式

Kong通过PCRE(Perl兼容正则表达式)支持Route的路径字段的正则表达式模式匹配。您可以同时将路径作为前缀和正则表达式分配给Route。例如,如果我们考虑以下Route:

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

此Route将匹配以下请求:

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

使用PCRE标志(PCRE_ANCHORED)评估提供的正则表达式,这意味着它们将被约束为在路径中的第一个匹配点(root/character)匹配。

评估顺序

如前所述,Kong按长度评估前缀路径:首先评估最长前缀路径。 但是,Kong将根据路由的regex_priority属性从最高优先级到最低优先级来评估正则表达式路径。这意味着考虑以下Routes:

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

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

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

始终在正则表达式路径之前评估前缀路径。

像往常一样,请求仍然必须匹配Route的机hostsmethods属性,并且Kong将遍历您的Routes,直到找到匹配最多规则的路由(请参阅[路由优先级] [代理路由优先级])。

捕获组

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

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

以及以下请求路径:

/version/1/users/john

Kong会将请求路径视为匹配,如果匹配整个Route(考虑hostsmethods字段),则可以从ngx.ctx变量中的插件获取提取的捕获组:

local router_matches = ngx.ctx.router_matches

-- router_matches.uri_captures is:
-- { "1", "john", version = "1", user = "john" }
规避特殊字符

接下来,值得注意的是,根据RFC 3986,在正则表达式中找到的字符通常是保留字符,因此应该是百分比编码(URL编码)。通过Admin API配置具有正则表达式路径的路由时,请务必在必要时对您的有效负载进行URL编码。例如,使用curl并使用application/x-www-form-urlencodeMIME类型:

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

请注意,curl不会自动对您的有效负载进行URL编码,并注意使用--data-urlencode,它可以防止Kong的Admin API对+字符进行URL解码,并将其解码为一个空的“。

strip_path属性

可能需要指定路径前缀以匹配路由,但不将其包括在上游请求中。为此,请通过配置如下所示的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: ...

请求HTTP方法

methods字段允许根据HTTP方法匹配请求。它接受多个值。其默认值为空(HTTP方法不用于路由)。 以下路由允许通过GETHEAD进行路由:

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

这样的Route将符合以下要求:

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

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

匹配优先事项

Route可以基于其hosts, paths, 和 methods字段定义匹配规则。要使Kong匹配到路由的传入请求,必须满足所有现有字段。 但是,通过允许两个或多个路由配置包含相同值的字段,Kong允许相当大的灵活性 – 当发生这种情况时,Kong应用优先级规则。

规则是:在评估请求时,Kong将首先尝试匹配具有最多规则的路由。

例如,如果两个路由配置如下:

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

第二个Route有一个hosts字段和一个methods字段,因此它将首先由Kong评估。通过这样做,我们避免了第一个用于第二个路径的“阴影”调用。

因此,此请求将匹配第一个Route

GET / HTTP/1.1
Host: example.com

这个请求将匹配第二个:

POST / HTTP/1.1
Host: example.com

遵循这个逻辑,如果要使用hosts字段,methods字段和uris字段配置第三个Route,它将首先由Kong评估。

代理行为

上面的代理规则详细说明了Kong如何将传入请求转发到您的上游服务。下面,我们详细说明Kong与HTTP请求与注册路由匹配的时间与请求的实际转发之间内部发生的情况。

1.负载均衡

Kong实现负载平衡功能,以跨上游服务实例池分发代理请求。

您可以通过查看负载平衡来查找有关配置负载平衡的更多信息

2.插件执行

Kong可通过“插件”进行扩展,这些“插件”将自己挂载在代理请求的请求/响应生命周期中。插件可以在您的环境中执行各种操作 和/或 在代理请求上进行转换。

可以将插件配置为全局(针对所有代理流量)或特定 Routes 和 Services运行。 在这两种情况下,您都必须通过Admin API创建插件配置

一旦路由匹配(及其关联的服务实体),Kong将运行与这些实体中的任何一个相关联的插件。在路由上配置的插件在服务上配置的插件之前运行,否则,通常的插件关联规则适用。

这些配置的插件将运行其access阶段,您可以在插件开发指南中找到更多相关信息。

3.代理和上游超时

一旦Kong执行了所有必要的逻辑(包括插件),它就可以将请求转发给您的上游服务。这是通过Nginx的ngx_http_proxy_module完成的。 您可以通过以下服务属性为Kong和给定上游之间的连接配置所需的超时:

  • upstream_connect_timeout:以毫秒为单位定义建立与上游服务的连接的超时。默认为60000
  • upstream_send_timeout:以毫秒为单位定义用于向上游服务发送请求的两个连续写入操作之间的超时。默认为60000
  • upstream_read_timeout:以毫秒为单位定义用于接收来自上游服务的请求的两个连续读取操作之间的超时。默认为60000

Kong将通过 HTTP/1.1 发送请求,并设置以下headers:

  • Host: <your_upstream_host>,如前文所述。
  • Connection: keep-alive,允许重用上游连接。
  • X-Real-IP: <remote_addr>,其中$remote_addr是ngx_http_core_module提供的名称相同的变量。请注意,$remote_addr可能被ngx_http_realip_module覆盖。
  • X-Forwarded-For: <address>,其中<address>是由附加到具有相同名称的请求标头的ngx_http_realip_module提供的$realip_remote_addr的内容。
  • X-Forwarded-Proto: <protocol>,其中<protocol>是客户端使用的协议。在$realip_remote_addr是可信地址之一的情况下,如果提供,则转发具有相同名称的请求头。否则,将使用ngx_http_core_module提供的$scheme变量的值。
  • X-Forwarded-Host: <host>,其中<host>是客户端发送的主机名。在$realip_remote_addr是可信地址之一的情况下,如果提供,则转发具有相同名称的请求头。否则,将使用ngx_http_core_module提供的$host变量的值。
  • X-Forwarded-Port: <port>,其中<port>是接受请求的服务器的端口。在$realip_remote_addr是可信地址之一的情况下,如果提供,则转发具有相同名称的请求头。否则,将使用ngx_http_core_module提供的$server_port变量的值。

所有其他请求headers都由Kong转发。

使用WebSocket协议时会出现一个例外。如果是这样,Kong将设置以下标头以允许升级客户端和上游服务之间的协议:

  • Connection: Upgrade
  • Upgrade: websocket

有关此主题的更多信息,请参见[Proxy WebSocket流量] [proxy-websocket]部分。

4.错误和重试

每当代理期间发生错误时,Kong将使用底层的Nginx重试机制将请求传递给下一个上游。

这里有两个可配置元素:

  1. 重试次数:可以使用retries属性为每个服务配置。有关详细信息,请参阅Admin API
  2. 究竟是什么构成错误:这里Kong使用Nginx默认值,这意味着在与服务器建立连接,向其传递请求或读取响应头时发生错误或超时。

第二个选项基于Nginx的proxy_next_upstream指令。此选项不能通过Kong直接配置,但可以使用自定义Nginx配置添加。有关详细信息,请参阅配置参考。

5.响应

Kong接收来自上游服务的响应,并以流方式将其发送回下游客户端。此时,Kong将执行添加到 Route 和/或 Service 的后续插件,这些插件在header_filter阶段实现一个钩子。

一旦执行了所有已注册插件的header_filter阶段,Kong将添加以下headers,并将完整的headers发送到客户端:

  • Via: kong/x.x.x,其中x.x.x是正在使用的Kong版本。
  • X-Kong-Proxy-Latency: <latency>,其中latency是Kong收到客户端请求和向上游服务发送请求之间的时间(以毫秒为单位)。
  • X-Kong-Upstream-Latency: <latency>,其中latency是Kong等待上游服务响应的第一个字节的时间(以毫秒为单位)。

将标题发送到客户端后,Kong将开始为实现body_filter钩子的 Route和/或Service 执行已注册的插件。由于Nginx的流媒体特性,可以多次调用此钩子。由这样的body_filter挂钩成功处理的上游响应的每个块被发送回客户端。您可以在插件开发指南中找到有关body_filter钩子的更多信息。

配置一个备用路由

作为Kong的代理功能提供的灵活性的实际用例和示例,让我们尝试实现“后备路线”,因此,为了避免Kong响应HTTP 404,“找不到路由”,我们可以捕获这些请求并将它们代理到特殊的上游服务,或者向它应用插件(例如,这样的插件可以使用不同的状态代码或响应终止请求,而不代理请求)。

以下是此类后备路由的示例:

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

正如您所猜测的,任何向Kong发出的HTTP请求实际上都会匹配此Route,因为所有URI都以根字符/为前缀。正如我们从[请求路径] [代理请求路径]部分所知,最长的URL路径首先由Kong评估,因此/路径最终将由Kong最后评估,并有效地提供“后备”路由,仅作为最后的手段。

为路由配置SSL

Kong提供了一种基于每个连接动态提供SSL证书的方法。SSL证书由核心直接处理,并可通过Admin API进行配置。通过TLS连接到Kong的客户端必须支持服务器名称指示扩展才能使用此功能。

SSL证书由Kong Admin API中的两个资源处理:

  • /certificates,存储您的密钥和证书。
  • /snis,将注册证书与Server Name 指示相关联。

您可以在Admin API参考中找到这两种资源的文档。

以下是在给定路由上配置SSL证书的方法:首先,通过Admin API上传您的SSL证书和密钥:

curl -i -X POST http://localhost:8001/certificates \
    -F "cert=@/path/to/cert.pem" \
    -F "key=@/path/to/cert.key" \
    -F "snis=ssl-example.com,other-ssl-example.com"
HTTP/1.1 201 Created
...

snis表单参数是糖参数,直接插入SNI并将上传的证书与其关联。

您现在必须在Kong内注册以下Route。 为方便起见,我们仅使用Hos header 匹配对此Route的请求:

curl -i -X POST http://localhost:8001/routes \
    -d 'hosts=ssl-example.com,other-ssl-example.com' \
    -d 'service.id=d54da06c-d69f-4910-8896-915c63c270cd'
HTTP/1.1 201 Created
...

您现在可以期望Kong通过HTTPS提供路由:

curl -i https://localhost:8443/ \
  -H "Host: ssl-example.com"
HTTP/1.1 200 OK
...

建立连接并协商SSL握手时,如果您的客户端发送ssl-example.com作为SNI扩展的一部分,Kong将提供先前配置的cert.pem证书。

限制客户端协议(HTTP/HTTPS/TCP/TLS)

路由具有protocols属性,以限制他们应该侦听的客户端协议。此属性接受一组值,可以是“http”“https”“tcp”“tls”

具有httphttps的路由将接受两种协议中的流量。

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["http", "https"],
    "service": {
        "id": "..."
    }
}

未指定任何协议具有相同的效果,因为路由默认为[“http”,“https”]

但是,仅使用https的路由接受通过HTTPS的流量。如果以前从受信任的IP发生SSL终止,它也会接受未加密的流量。当请求来自trusted_ip中的一个配置的IP并且如果设置了X-Forwarded-Proto:https header时,SSL终止被认为是有效的:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["https"],
    "service": {
        "id": "..."
    }
}

如果上述路由与请求匹配,但该请求是纯文本而没有有效的先前SSL终止,则Kong响应:

HTTP/1.1 426 Upgrade Required
Content-Type: application/json; charset=utf-8
Transfer-Encoding: chunked
Connection: Upgrade
Upgrade: TLS/1.2, HTTP/1.1
Server: kong/x.y.z

{"message":"Please use HTTPS protocol"}

从Kong 1.0开始,可以使用protocols属性中的“tcp”为原始TCP(不一定是HTTP)连接创建路由:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tcp"],
    "service": {
        "id": "..."
    }
}

同样,我们可以使用“tls”值创建接受原始TLS流量(不一定是HTTPS)的路由:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tls"],
    "service": {
        "id": "..."
    }
}

仅具有TLS的路由仅接受通过TLS的流量。 也可以同时接受TCP和TLS:

{
    "hosts": ["..."],
    "paths": ["..."],
    "methods": ["..."],
    "protocols": ["tcp", "tls"],
    "service": {
        "id": "..."
    }
}

代理WebSocket流量

由于底层的Nginx实现,Kong支持WebSocket流量。如果希望通过Kong在客户端和上游服务之间建立WebSocket连接,则必须建立WebSocket握手。这是通过HTTP升级机制完成的。这是您的客户要求对Kong的看法:

GET / HTTP/1.1
Connection: Upgrade
Host: my-websocket-api.com
Upgrade: WebSocket

这将使Kong将ConnectionUpgrade header 转发到您的上游服务,而不是由于标准HTTP代理的逐跳特性而将其解除。

WebSocket和TLS

Kong将在其各自的httphttps端口接受wswss连接。要从客户端强制执行TLS连接,请将Route的protocols属性设置为https

将 Service 设置为指向上游WebSocket服务时,应仔细选择要在Kong和上游之间使用的协议。如果要使用TLS(wss),则必须使用服务protocol属性中的https协议和正确的端口(通常为443)定义上游WebSocket服务。要在没有TLS(ws)的情况下进行连接,则应在协议中使用http协议和端口(通常为80)。

如果您希望Kong终止SSL/TLS,您只能从客户端接受wss,而是通过纯文本或ws代理上游服务。

结论

通过本指南,我们希望您了解Kong的基础代理机制,从请求如何匹配路由到其关联的服务,到如何允许使用WebSocket协议或设置动态SSL证书。

该网站是开源的,可以在 https://github.com/Kong/docs.konghq.com 找到。 如果您还没有,我们建议您还阅读负载平衡参考,因为它与我们刚刚介绍的主题密切相关。

Lua OpenResty 使用rabbitmq AMQP协议发送和接收消息

如果想要在openresty中使用AMQP协议发送和接收消息的话,需要使用到一个第三方库。地址为:https://github.com/mengz0/amqp 。 当然,首先你得在自己的环境中安装一个rabbitmq,或者使用远程的rabbitmq也可以。 openresty也是要安装一下的,这里就不啰嗦了。

第一步:安装第三方库

luarocks install amqp

如果没有安装 luarocks 可以安装一下luarocks。

第二步:使用amqp发送&接收消息

可以直接参考https://github.com/mengz0/amqp 中的例子,但是这个库的例子写的非常简略,导致很多参数需要你自己去源码中查看,我在这个例子中列举了一些常用的参数,可以参考使用一下。

发送消息

local function send_pb(messages)
    local amqp        = require "amqp"

    local ctx = amqp.new({
        role        = "publisher",
        exchange    = "exchangexxxx",
        ssl         = false,
        user        = "guest",
        password    = "guest",
        auto_delete = false,
        routing_key = "routing_keyxxxxx",
        passive     = true,
        no_ack      = true,
        no_wait     = false,
    })
    ctx:connect("127.0.0.1", port)
    ctx:setup()
    local ok, err = ctx:publish(messages)
    if not ok then
        ngx.log(ngx.ERR, "[ -- rabbitmq send failed : -- ] " .. err)
    else
        ngx.log(ngx.ERR, "[ -- rabbitmq send success ]")
    end
end

send_pb("this is a message")

接收消息&消费队列

可以新建一个consume_queue.lua文件,然后如下代码:

local function consume_local(body)
    print(body) -- 这里就是消息的主体
end

local amqp        = require "amqp"

local ctx = amqp.new({
    role        = "consumer",
    queue       = "eventbus1", -- 这里可以自定义
    exchange    = "exchangexxxx",
    ssl         = false,
    user        = "guest",
    password    = "guest",
    no_wait     = false,
    routing_key = "routing_keyxxxxx",
    auto_delete = false, -- 是否自动删除消息
    no_ack      = true,
    exclusive   = false, -- 是否为排他队列
    callback    = consume_local,
    durable     = true,
    passive     = false,
    type        = "topic"

})

ctx:connect("127.0.0.1", port)

local ok, err = ctx:consume()

然后使用命令/usr/local/openresty/bin/resty consume_queue.lua,就可以看到发送的消息了。

发现问题

在使用这个库的时候,发现了一点小问题,就是在消费这个队列的时候,发现诸如passiveauto_deletedurable等等参数设置的全都无效了,但是感觉自己的使用姿势没有问题啊,按照 https://github.com/mengz0/amqp#typical-use-cases 给的例子来也没问题。随后我就查看并断点了一下源码的https://github.com/mengz0/amqp/blob/master/amqp.lua 文件,发现在执行到 664行的function amqp:queue_declare(opts)函数和 687行的function amqp:queue_bind(opts)函数的时候,opts参数根本没有传进来。

解决问题

所以要把function amqp:queue_declare(opts)函数中的opts.改为self.opts.,如下:

f.method = {
      queue = opts.queue or self.opts.queue,
      passive = opts.passive or false,
      durable = opts.durable or false,
      exclusive = opts.exclusive or false,
      auto_delete = opts.auto_delete or true,
      no_wait = self.opts.no_wait or true
   }

修改为

f.method = {
      queue = opts.queue or self.opts.queue,
      passive = self.opts.passive or false,
      durable = self.opts.durable or false,
      exclusive = self.opts.exclusive or false,
      auto_delete = self.opts.auto_delete or true,
      no_wait = self.opts.no_wait or true
   }

function amqp:queue_bind(opts)函数也同理,这里就不再写一次了,修改完了之后,传进去的参数都生效了。

打完收工。

在 lua 中使用 protobuf

安装protobuf

具体步骤可以参考官方文档https://github.com/protocolbuffers/protobuf/blob/master/src/README.md

这里只列出一些其中比较的重要的步骤。

先安装一些依赖的库

sudo apt-get install autoconf automake libtool curl make g++ unzip

然后下载需要的安装包

curl -L -o protobuf-all-3.6.1.tar.gz  https://github.com/protocolbuffers/protobuf/releases/download/v3.6.1/protobuf-all-3.6.1.tar.gz

接着解压

tar -xzvf protobuf-all-3.6.1.tar.gz

解压完成后进目录

cd protobuf-all-3.6.1

执行安装命令

./configure
make
make check
sudo make install
sudo ldconfig

最后检查是否安装成功

$ protoc --version
libprotoc 3.6.1

安装lua-protobuf

lua-protobuf 实际上是一个纯C的protobuf协议实现,和对应的Lua绑定。

项目地址:https://github.com/starwing/lua-protobuf

可以使用 luarocks 安装lua-protobuf

luarocks install lua-protobuf

如果没有安装 luarocks 可以安装一下luarocks。

使用prortobuf

首先,需要定义你自己的标准proto文件 xxx.proto 文件(可能不止一个文件,不止一个目录),定义好了之后,使用protoc生成pb文件,命令如下:

protoc --proto_path=proto --descriptor_set_out=common.pb proto/xxxx/*.proto proto/xxxxx/*.proto

这里需要注意的是,有时候会有多个proto文件并且多目录import的情况,这个时候,就需要在参数中都体现出来(命令的最后两个参数),这条命令是把所有的 .proto 文件生成了一个 common.pb 文件方便引入。

最后就可以在lua代码中如下使用

-- 引入pb库
local pb = require "pb"

-- load pb文件
assert(pb.loadfile "common.pb" )

-- lua table data
local data = {
    aaa = 123456,
    bbb = "bbbbb"
}

-- 把一个lua table数据encode成二进制文件
local bytes = assert(pb.encode("dd01.account", data))
print(pb.tohex(bytes))

-- 把一个二进制文件decode为lua table
local data2 = assert(pb.decode("dd01.account", bytes))
print(cjson_encode(data2))

如果能看到正常打印出来,说明就成功了。

参考链接

微服务 API 网关 Kong 插件开发 – 插件配置

原文地址:https://docs.konghq.com/1.1.x/plugin-development/plugin-configuration/(不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

简介

大多数情况下,您的插件可以配置为满足您的所有用户需求。当插件被执行的时候,您的插件的配置存储在Kong的数据存储区中,以检索它并将其传递给handler.lua方法。

配置由Kong中的Lua表组成,我们称之为 schema。它包含用户在通过Admin API启用插件时将设置的键/值属性。Kong为您提供了一种验证用户插件配置的方法。

当用户向Admin API发出请求以启用或更新给定Service,Route和/或Consumer上的插件时,将根据您的架构schema插件的配置。

例如,用户执行以下请求:

$ curl -X POST http://kong:8001/services/<service-name-or-id>/plugins/ \
    -d "name=my-custom-plugin" \
    -d "config.foo=bar"

如果配置对象的所有config都根据您的模式有效,则API将返回201 Created,并且插件将与其配置一起存储在数据库中(在这种情况下为{foo =“bar”})。如果配置无效,Admin API将返回400 Bad Request和相应的错误消息。

模块

kong.plugins.<plugin_name>.schema

schema.lua规范

此模块将返回一个Lua表,其中包含将定义用户以后如何配置插件的属性的属性。 可用的属性是:

属性名称 Lua type 默认值 描述
no_consumer Boolen false 如果为true,则无法将此插件应用于特定的Consumer。此插件必须仅应用于服务和路由。例如:身份验证插件。
fields Table {} 你插件的schema,可用属性及其规则的键/值表。
self_check Function nil 如果要在接受插件配置之前执行任何自定义验证,则要实现的功能。

self_check函数必须按如下方式实现:

-- @param `schema` 描述插件配置的架构(规则)的表。
-- @param `config` 当前插件配置的键/值表。
-- @param `dao` DAO的一个实例 (查看 DAO 章节).
-- @param `is_updating` 一个布尔值,指示是否在更新的上下文中执行此检查。
-- @return `valid` 一个布尔值,指示插件的配置是否有效。
-- @return `error` 一个 DAO 错误 (查看 DAO 章节)

以下是一个可能的schema.lua文件的示例:

return {
  no_consumer = true, -- 此插件仅适用于服务或路由,
  fields = {
    -- 在此处描述您的插件配置架构。
  },
  self_check = function(schema, plugin_t, dao, is_updating)
    -- 执行任何自定义验证
    return true
  end
}

描述您的配置schema

schema.lua文件的fields自选描述了插件配置的schema。它是一个灵活的键/值表,其中每个键都是插件的有效配置属性,每个键都是一个描述该属性规则的表。例如:

 fields = {
    some_string = {type = "string", required = true},
    some_boolean = {type = "boolean", default = false},
    some_array = {type = "array", enum = {"GET", "POST", "PUT", "DELETE"}}
  }

以下是属性的规则列表:

规则 LUA TYPE(S) 可使用的值 描述
type string “id”, “number”, “boolean”, “string”, 
“table”, “array”, “url”, “timestamp”
验证属性的类型。
required boolean 默认值:false。
如果为true,则该属性必须存在于配置中。
unique boolean 默认值:false。
如果为true,则该值必须是唯一的(请参阅下面的注释)。
default any 如果未在配置中指定该属性,则将该属性设置为给定值。
immutable boolean 默认值:false。
如果为true,则在创建插件配置后将不允许更新该属性。
enum table 属性的可接受值列表。不接受此列表中未包含的任何值。
regex string 用于验证属性值的正则表达式。
schema table 如果属性的类型是table,则定义用于验证这些子属性的模式。
func function 用于对属性执行任何自定义验证的函数。请参阅后面的示例,了解其参数和返回值。
  • type:将转换从请求参数中检索的值。如果类型不是本机Lua类型之一,则会对其执行自定义验证:
    • id:必须是string
    • timestamp:必须是nember
    • uri:必须是有效的URL
    • array:必须是整数索引表(相当于Lua中的数组)。在Admin API中,可以通过在请求的正文中使用不同值的属性键的多次来发送这样的数组,或者通过单个body参数以逗号分隔。
  • unique:此属性对插件配置没有意义,但在插件需要在数据存储区中存储自定义实体时使用。
  • schema:如果您需要对嵌套属性进行深化验证,则此字段允许您创建嵌套模式。模式验证是递归的。任何级别的嵌套都是有效的,但请记住,这会影响插件的可用性。
  • 附加到配置对象但schema中不存在的任何属性也将使所述配置无效。

例子

key-auth插件的schema.lua文件定义了API密钥的可接受参数名称的默认列表,以及默认设置为false的布尔值:

-- schema.lua
return {
  no_consumer = true,
  fields = {
    key_names = {type = "array", required = true, default = {"apikey"}},
    hide_credentials = {type = "boolean", default = false}
  }
}

于是,当在handler.lua中实现插件的access()函数并且用户使用默认值启用插件时,您可以如下:

-- handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()

function CustomHandler:new()
  CustomHandler.super.new(self, "my-custom-plugin")
end

function CustomHandler:access(config)
  CustomHandler.super.access(self)

  kong.log.inspect(config.key_names)        -- {"apikey"}
  kong.log.inspect(config.hide_credentials) -- false
end

return CustomHandler

请注意,上面的示例使用插件开发工具包(PDK)kong.log.inspect函数将这些值打印到Kong日志中。

一个更复杂的示例,可用于最终日志记录插件:

-- schema.lua

local function server_port(given_value, given_config)
  -- 自定义验证
  if given_value > 65534 then
    return false, "port value too high"
  end

  -- 如果环境是“开发”,8080将是默认端口
  if given_config.environment == "development" then
    return true, nil, {port = 8080}
  end
end

return {
  fields = {
    environment = {type = "string", required = true, enum = {"production", "development"}}
    server = {
      type = "table",
      schema = {
        fields = {
          host = {type = "url", default = "http://example.com"},
          port = {type = "number", func = server_port, default = 80}
        }
      }
    }
  }
}

这样的配置将允许用户将配置发布到您的插件,如下所示:

curl -X POST http://kong:8001/services/<service-name-or-id>/plugins \
    -d "name=my-custom-plugin" \
    -d "config.environment=development" \
    -d "config.server.host=http://localhost"

以下内容将在handler.lua中提供:

-- handler.lua
local BasePlugin = require "kong.plugins.base_plugin"
local CustomHandler = BasePlugin:extend()

function CustomHandler:new()
  CustomHandler.super.new(self, "my-custom-plugin")
end

function CustomHandler:access(config)
  CustomHandler.super.access(self)

  kong.log.inspect(config.environment) -- "development"
  kong.log.inspect(config.server.host) -- "http://localhost"
  kong.log.inspect(config.server.port) -- 8080
end

return CustomHandler

您还可以在Key-Auth插件源代码中查看schema的真实示例。

微服务 API 网关 Kong 插件开发 – 文件结构

原文地址:https://docs.konghq.com/1.1.x/plugin-development/file-structure/ (不能保证所有的翻译都是准确无误的,所有如有翻译的不准确或错误之处,请一定记得查看原文,并欢迎留言指出)。

本章假定你已经会使用Lua语言

介绍

将您的插件视为一组Lua模块。本章中描述的每个文件都被视为一个单独的模块。如果他们的名字遵循这个约定,Kong将检测并加载你的插件的模块:

kong.plugins.<plugin_name>.<module_name>

您的模块当然需要通过package.path变量访问,可以通过lua_package_path配置属性调整您的需求。但是,安装插件的首选方法是通过LuaRocks,它与Kong本身集成。有关LuaRocks安装的插件的更多信息,请参阅本指南后面的内容。

为了让Kong意识到必须查找插件的模块,你必须将它添加到配置文件中的plugins属性中,这是一个以逗号分隔的列表。例如

plugins = bundled,my-custom-plugin # 你的插件名称

或者,如果您不想加载任何自带的插件:

plugins = my-custom-plugin  # 你的插件名称

现在,Kong将尝试从以下命名空间加载几个Lua模块:

kong.plugins.my-custom-plugin.<module_name>

其中一些模块是必需的(例如handler.lua),有些是可选的,并且允许插件实现一些额外的功能(例如api.lua以扩展Admin API)。现在让我们准确描述您可以实现的模块以及它们的用途。

基本插件模块

在最基本的形式中,插件包含两个必需的模块:

simple-plugin
├── handler.lua
└── schema.lua
  • 每个函数将在请求的生命周期中的所需时刻运行。
  • 由用户。此模块保存该配置的模式并在其上定义规则,以便用户只能输入有效的配置值。

高级插件模块

有些插件可能需要与Kong更深入地集成:在数据库中拥有自己的表,在Admin API中公开端点等等……每个插件都可以通过向插件添加新模块来完成。如果它实现了所有可选模块,那么插件的结构如下:

complete-plugin
├── api.lua
├── daos.lua
├── handler.lua
├── migrations
│   ├── cassandra.lua
│   └── postgres.lua
└── schema.lua

以下是要实施的可能模块的完整列表以及其目的的简要说明。 本指南将详细介绍,让您掌握其中的每一个文件。

模块文件名称 是否必须 描述
api.lua No 定义Admin API中可用的端点列表,以与插件处理的实体自定义实体进行交互。
daos.lua No 定义DAO(数据库访问对象)列表,这些DAO是插件所需并存储在数据存储区中的自定义实体的抽象。
handler.lua Yes 一个接口的实现。每个函数都由Kong在请求的生命周期中的所需时刻运行。
migrations/xxxx.lua No 给定数据存储的相应迁移。只有当您的插件必须在数据库中存储自定义实体并通过daos.lua定义的其中一个DAO与它们进行交互时,才需要进行迁移。
schema.lua Yes 保存插件配置的架构,以便用户只能输入有效的配置值。

Key-Auth 插件是具有此文件结构的插件的示例。 有关详细信息,请参阅其源代码

Openresty 第三方库 Lua_resty_http 使用教程

lua_resty_http是一个第三方 openresty 库,基于 Openresty/ngx_lua 的HTTP客户端,支持POST方法上传数据,刚好项目中用到需要从网关中发起请求,于是就用到这个库,把使用方式在这里分享一下。

安装第三方库lua_resty_http

第一步

首先找到项目地址:https://github.com/pintsized/lua-resty-http

第二步

然后将 lua-resty-http/lib/resty/ 目录下的 http.lua  http_headers.lua 两个文件拷贝到 /usr/local/openresty/lualib/resty 目录下即可,OpenResty 安装目录为 /usr/local/openresty)。不需要重启。(少数需要清空 Openresty shared_dict 数据的情况需要重启 )。

代码示例

local function HttpUtil(jwt_data)
    -- 引入第三方库
    local http = require "resty.http"
    local httpc = http.new()
    -- 需要post的参数
    local str = "aaa=1&bbb=2%ccc=3"
    -- 请求地址
    local url = "http://wwww.xxxxxxx.com"
    local res, err = httpc:request_uri(url, {
        method = "POST",
        body = str,
        headers = {
            ["Content-Type"] = "application/x-www-form-urlencoded",
            ["Content-Length"] = #str,
        }
    })

    -- 返回值
    ngx.say(res.body)
    ngx.say(res.status)
    ngx.say(res.headers)

    return res.body,res.status
end

参数详解

请求参数

参数名 描述
version HTTP版本号,目前支持1.0或1.1
method HTTP方法
path 路径
query 查询字符串,表示为文字字符串或Lua table
headers 请求 header table
body 请求体作为字符串或迭代器函数(请参阅get_client_body_reader)。
ssl_verify 验证SSL证书是否与主机名匹配

返回参数

请求成功后,res将包含以下字段:

参数名 描述
status 状态码
reason 状态原因短语
headers headers 类型是table ,相同名称的header会合并成一个table
has_body bool值,指示是否有要读取的正文
body_reader 迭代器函数,用于以流方式读取正文。
read_body 一种将整个主体读入字符串的方法。
read_trailers 读取正文后,合并标题下trailers的方法。

参考链接