Skip to content

Commit

Permalink
feature: add batch request plugin. (#1388)
Browse files Browse the repository at this point in the history
  • Loading branch information
ShiningRush authored Apr 29, 2020
1 parent c6cc2b5 commit 3a9e0fc
Show file tree
Hide file tree
Showing 11 changed files with 1,204 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ A/B testing, canary release, blue-green deployment, limit rate, defense against
- IPv6: Use IPv6 to match route.
- Support [TTL](doc/admin-api-cn.md#route)
- [Support priority](doc/router-radixtree.md#3-match-priority)
- [Support Batch Http Requests](doc/plugins/batch-requests.md)

- **Security**
- Authentications: [key-auth](doc/plugins/key-auth.md), [JWT](doc/plugins/jwt-auth.md), [basic-auth](doc/plugins/basic-auth.md), [wolf-rbac](doc/plugins/wolf-rbac.md)
Expand Down
1 change: 1 addition & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ A/B 测试、金丝雀发布(灰度发布)、蓝绿部署、限流限速、抵
- IPv6:支持使用 IPv6 格式匹配路由
- 支持路由的[自动过期(TTL)](doc/admin-api-cn.md#route)
- [支持路由的优先级](doc/router-radixtree.md#3-match-priority)
- [支持批量 Http 请求](doc/plugins/batch-requests-cn.md)

- **安全防护**
- 多种身份认证方式: [key-auth](doc/plugins/key-auth-cn.md), [JWT](doc/plugins/jwt-auth-cn.md), [basic-auth](doc/plugins/basic-auth-cn.md), [wolf-rbac](doc/plugins/wolf-rbac-cn.md)
Expand Down
241 changes: 241 additions & 0 deletions apisix/plugins/batch-requests.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
--
-- Licensed to the Apache Software Foundation (ASF) under one or more
-- contributor license agreements. See the NOTICE file distributed with
-- this work for additional information regarding copyright ownership.
-- The ASF licenses this file to You under the Apache License, Version 2.0
-- (the "License"); you may not use this file except in compliance with
-- the License. You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
local core = require("apisix.core")
local http = require("resty.http")
local ngx = ngx
local io_open = io.open
local ipairs = ipairs
local pairs = pairs

local plugin_name = "batch-requests"

local schema = {
type = "object",
additionalProperties = false,
}

local req_schema = {
type = "object",
properties = {
query = {
description = "pipeline query string",
type = "object"
},
headers = {
description = "pipeline header",
type = "object"
},
timeout = {
description = "pipeline timeout(ms)",
type = "integer",
default = 30000,
},
pipeline = {
type = "array",
minItems = 1,
items = {
type = "object",
properties = {
version = {
description = "HTTP version",
type = "number",
enum = {1.0, 1.1},
default = 1.1,
},
method = {
description = "HTTP method",
type = "string",
enum = {"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD",
"OPTIONS", "CONNECT", "TRACE"},
default = "GET"
},
path = {
type = "string",
minLength = 1,
},
query = {
description = "request header",
type = "object",
},
headers = {
description = "request query string",
type = "object",
},
ssl_verify = {
type = "boolean",
default = false
},
}
}
}
},
anyOf = {
{required = {"pipeline"}},
},
}

local _M = {
version = 0.1,
priority = 4010,
name = plugin_name,
schema = schema
}


function _M.check_schema(conf)
local ok, err = core.schema.check(schema, conf)
if not ok then
return false, err
end
return true
end


local function check_input(data)
local ok, err = core.schema.check(req_schema, data)
if not ok then
return 400, {error_msg = "bad request body: " .. err}
end
end


local function set_common_header(data)
if not data.headers then
return
end

for i,req in ipairs(data.pipeline) do
if not req.headers then
req.headers = data.headers
else
for k, v in pairs(data.headers) do
if not req.headers[k] then
req.headers[k] = v
end
end
end
end
end


local function set_common_query(data)
if not data.query then
return
end

for i,req in ipairs(data.pipeline) do
if not req.query then
req.query = data.query
else
for k, v in pairs(data.query) do
if not req.query[k] then
req.query[k] = v
end
end
end
end
end


local function get_file(file_name)
local f = io_open(file_name, 'r')
if f then
local req_body = f:read("*all")
f:close()
return req_body
end

return
end


local function batch_requests()
ngx.req.read_body()
local req_body = ngx.req.get_body_data()
if not req_body then
local file_name = ngx.req.get_body_file()
if file_name then
req_body = get_file(file_name)
end

if not req_body then
core.response.exit(400, {
error_msg = "no request body, you should give at least one pipeline setting"
})
end
end

local data, err = core.json.decode(req_body)
if not data then
core.response.exit(400, {
error_msg = "invalid request body: " .. req_body .. ", err: " .. err
})
end

local code, body = check_input(data)
if code then
core.response.exit(code, body)
end

local httpc = http.new()
httpc:set_timeout(data.timeout)
local ok, err = httpc:connect("127.0.0.1", ngx.var.server_port)
if not ok then
core.response.exit(500, {error_msg = "connect to apisix failed: " .. err})
end

set_common_header(data)
set_common_query(data)
local responses, err = httpc:request_pipeline(data.pipeline)
if not responses then
core.response.exit(400, {error_msg = "request failed: " .. err})
end

local aggregated_resp = {}
for _, resp in ipairs(responses) do
if not resp.status then
core.table.insert(aggregated_resp, {
status = 504,
reason = "upstream timeout"
})
end
local sub_resp = {
status = resp.status,
reason = resp.reason,
headers = resp.headers,
}
if resp.has_body then
sub_resp.body = resp:read_body()
end
core.table.insert(aggregated_resp, sub_resp)
end
core.response.exit(200, aggregated_resp)
end


function _M.api()
return {
{
methods = {"POST"},
uri = "/apisix/batch-requests",
handler = batch_requests,
}
}
end


return _M
1 change: 1 addition & 0 deletions conf/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,5 +145,6 @@ plugins: # plugin list
- proxy-mirror
- kafka-logger
- cors
- batch-requests
stream_plugins:
- mqtt-proxy
1 change: 1 addition & 0 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Plugins
* [proxy-mirror](plugins/proxy-mirror.md): Provides the ability to mirror client requests.
* [kafka-logger](plugins/kafka-logger.md): Log requests to External Kafka servers.
* [cors](plugins/cors.md): Enable CORS(Cross-origin resource sharing) for your API.
* [batch-requests](plugins/batch-requests.md): Allow you send mutiple http api via **http pipeline**.

Deploy to the Cloud
=======
Expand Down
1 change: 1 addition & 0 deletions doc/README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ Reference document
* [tcp-logger](plugins/tcp-logger.md): 将请求记录到TCP服务器
* [kafka-logger](plugins/kafka-logger-cn.md): 将请求记录到外部Kafka服务器。
* [cors](plugins/cors-cn.md): 为你的API启用CORS.
* [batch-requests](plugins/batch-requests-cn.md): 以 **http pipeline** 的方式在网关一次性发起多个 `http` 请求。
Loading

0 comments on commit 3a9e0fc

Please sign in to comment.