Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[batch-request]feat: cp all header to every request #1697

Merged
merged 2 commits into from
Jun 16, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions apisix/plugins/batch-requests.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@
-- 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 core = require("apisix.core")
local http = require("resty.http")
local ngx = ngx
local io_open = io.open
local ipairs = ipairs
local pairs = pairs
local str_find = string.find
local str_lower = string.lower


local plugin_name = "batch-requests"

Expand Down Expand Up @@ -112,22 +115,42 @@ local function check_input(data)
end
end

local function lowercase_key_or_init(obj)
if not obj then
return {}
end

local function set_common_header(data)
local ck = core.request.header(nil, "Cookie")
local lowercase_key_obj = {}
for k, v in pairs(obj) do
lowercase_key_obj[str_lower(k)] = v
end

return lowercase_key_obj
end

local function ensure_header_lowercase(data)
data.headers = lowercase_key_or_init(data.headers)

for i,req in ipairs(data.pipeline) do
if not req.headers then
req.headers = {}
end
req.headers = lowercase_key_or_init(req.headers)
end
end

if ck then
req.headers["Cookie"] = ck

local function set_common_header(data)
local outer_headers = core.request.headers(nil)
for i,req in ipairs(data.pipeline) do
for k, v in pairs(data.headers) do
if not req.headers[k] then
req.headers[k] = v
end
end

if data.headers then
for k, v in pairs(data.headers) do
if not req.headers[k] then
if outer_headers then
for k, v in pairs(outer_headers) do
local is_content_header = str_find(k, "content-", 1, true) == 1
-- skip header start with "content-"
if not req.headers[k] and not is_content_header then
req.headers[k] = v
end
end
Expand Down Expand Up @@ -202,8 +225,10 @@ local function batch_requests()
core.response.exit(500, {error_msg = "connect to apisix failed: " .. err})
end

ensure_header_lowercase(data)
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})
Expand Down
4 changes: 4 additions & 0 deletions doc/plugins/batch-requests-cn.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@

`batch-requests` 插件可以一次接受多个请求并以 [http pipeline](https://en.wikipedia.org/wiki/HTTP_pipelining) 的方式在网关发起多个http请求,合并结果后再返回客户端,这在客户端需要访问多个接口时可以显著地提升请求性能。

> **提示**
>
> 外层的 Http 请求头会自动设置到每一个独立请求中,如果独立请求中出现相同键值的请求头,那么只有独立请求的请求头会生效。

## 属性

Expand Down
6 changes: 5 additions & 1 deletion doc/plugins/batch-requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@

`batch-requests` can accept mutiple request and send them from `apisix` via [http pipeline](https://en.wikipedia.org/wiki/HTTP_pipelining),and return a aggregated response to client,this can significantly improve performance when the client needs to access multiple APIs.

> **Tips**
>
> The HTTP headers for the outer batch request, except for the Content- headers such as Content-Type, apply to every request in the batch. If you specify a given HTTP header in both the outer request and an individual call, then the individual call header's value overrides the outer batch request header's value. The headers for an individual call apply only to that call.

## Attributes

None
Expand All @@ -41,7 +45,7 @@ None
Default enbaled

## Batch Api Request/Response
The plugin will create a api in `apisix` to handle your aggregation request.
The plugin will create a api in `apisix` to handle your batch request.

### Batch Api Request:

Expand Down
37 changes: 25 additions & 12 deletions t/plugin/batch-requests.t
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,16 @@ __DATA__
},
"headers": {
"Base-Header": "base",
"Conflict-Header": "header_value"
"ConflictHeader": "header_value",
"OuterConflict": "common_value"
},
"pipeline":[
{
"path": "/b",
"headers": {
"Header1": "hello",
"Header2": "world",
"Conflict-Header": "b-header-value"
"ConflictHeader": "b-header-value"
}
},{
"path": "/c",
Expand All @@ -71,7 +72,8 @@ __DATA__
"X-Res": "B",
"X-Header1": "hello",
"X-Header2": "world",
"X-Conflict-Header": "b-header-value"
"X-Conflict-Header": "b-header-value",
"X-OuterConflict": "common_value"
}
},
{
Expand All @@ -95,8 +97,11 @@ __DATA__
"X-Query-Conflict": "d_value"
}
}
]]=]
)
]]=],
{
ConflictHeader = "outer_header",
OuterConflict = "outer_confliect"
})

ngx.status = code
ngx.say(body)
Expand All @@ -110,7 +115,8 @@ __DATA__
ngx.header["Base-Query"] = ngx.var.arg_base
ngx.header["X-Header1"] = ngx.req.get_headers()["Header1"]
ngx.header["X-Header2"] = ngx.req.get_headers()["Header2"]
ngx.header["X-Conflict-Header"] = ngx.req.get_headers()["Conflict-Header"]
ngx.header["X-Conflict-Header"] = ngx.req.get_headers()["ConflictHeader"]
ngx.header["X-OuterConflict"] = ngx.req.get_headers()["OuterConflict"]
ngx.header["X-Res"] = "B"
ngx.print("B")
}
Expand Down Expand Up @@ -687,7 +693,7 @@ passed



=== TEST 15: copy cookie to every request
=== TEST 15: copy all header to every request except Contenct-
--- config
client_body_in_file_only on;
location = /aggregate {
Expand All @@ -697,7 +703,7 @@ passed
local code, body = t('/apisix/batch-requests',
ngx.HTTP_POST,
[=[{
"timeout": 100,
"timeout": 1000,
"pipeline":[
{
"path": "/b",
Expand All @@ -716,24 +722,28 @@ passed
{
"status": 200,
"headers": {
"X-Cookie": "request-cookies-b"
"X-Cookie": "request-cookies-b",
"X-HeaderB": "request-header-b"
}
},
{
"status": 201,
"headers": {
"X-Cookie": "request-cookies-c"
"X-Cookie": "request-cookies-c",
"X-HeaderC": "request-header-c"
}
},
{
"status": 202,
"headers": {
"X-Cookie": "request-cookies-d"
"X-Cookie": "request-cookies-d",
"X-HeaderD": "request-header-d"
}
}
]]=],
{
Cookie = "request-cookies"
Cookie = "request-cookies",
Header = "request-header"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that is not a good name, we'd better use a different one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean we should not name key like "Cookie" or "Header"?But browner will send cookie into the http header named "Cookie", it is http specification.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Header is not a good name. Cookie is fine.

})

ngx.status = code
Expand All @@ -745,18 +755,21 @@ passed
content_by_lua_block {
ngx.status = 200
ngx.header["X-Cookie"] = ngx.req.get_headers()["Cookie"] .. "-b"
ngx.header["X-HeaderB"] = ngx.req.get_headers()["Header"] .. "-b"
}
}
location = /c {
content_by_lua_block {
ngx.status = 201
ngx.header["X-Cookie"] = ngx.req.get_headers()["Cookie"] .. "-c"
ngx.header["X-HeaderC"] = ngx.req.get_headers()["Header"] .. "-c"
}
}
location = /d {
content_by_lua_block {
ngx.status = 202
ngx.header["X-Cookie"] = ngx.req.get_headers()["Cookie"] .. "-d"
ngx.header["X-HeaderD"] = ngx.req.get_headers()["Header"] .. "-d"
}
}
--- request
Expand Down