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

feat: route accroding to the graphql attributes #2964

Merged
merged 4 commits into from
Dec 10, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
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
1 change: 1 addition & 0 deletions apisix/cli/env.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ return function (apisix_home, pkg_cpath_org, pkg_path_org)
.. apisix_home .. "/deps/lib/lua/5.1/?.so;"

local pkg_path = apisix_home .. "/?/init.lua;"
.. apisix_home .. "/deps/share/lua/5.1/?/init.lua;"
.. apisix_home .. "/deps/share/lua/5.1/?.lua;;"

package.cpath = pkg_cpath .. package.cpath
Expand Down
83 changes: 82 additions & 1 deletion apisix/core/ctx.lua
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,98 @@
-- limitations under the License.
--
local core_str = require("apisix.core.string")
local core_tab = require("apisix.core.table")
local request = require("apisix.core.request")
local log = require("apisix.core.log")
local config_local = require("apisix.core.config_local")
local tablepool = require("tablepool")
local get_var = require("resty.ngxvar").fetch
local get_request = require("resty.ngxvar").request
local ck = require "resty.cookie"
local gq_parse = require("graphql").parse
local setmetatable = setmetatable
local sub_str = string.sub
local rawset = rawset
local ngx = ngx
local ngx_var = ngx.var
local re_gsub = ngx.re.gsub
local ipairs = ipairs
local type = type
local error = error
local ngx = ngx
local pcall = pcall


local _M = {version = 0.2}
local GRAPHQL_DEFAULT_MAX_SIZE = 1048576 -- 1MiB


local function parse_graphql(ctx)
local local_conf, err = config_local.local_conf()
if not local_conf then
return nil, "failed to get local conf: " .. err
end

local max_size = GRAPHQL_DEFAULT_MAX_SIZE
local size = core_tab.try_read_attr(local_conf, "graphql", "max_size")
if size then
max_size = size
end

local body, err = request.get_body(max_size, ctx)
if not body then
return nil, "failed to read graphql body: " .. err
end

local ok, res = pcall(gq_parse, body)
if not ok then
return nil, "failed to parse graphql: " .. res .. " body: " .. body
end

if #res.definitions == 0 then
return nil, "empty graphql: " .. body
end

return res
end


local function get_parsed_graphql(ctx)
if ctx._graphql then
return ctx._graphql
end

local res, err = parse_graphql(ctx)
if not res then
log.error(err)
ctx._graphql = {}
return ctx._graphql
end

if #res.definitions > 1 then
log.warn("Mutliple operations are not supported.",
"Only the first one is handled")
end

local def = res.definitions[1]
local fields = def.selectionSet.selections
local root_fields = core_tab.new(#fields, 0)
for i, f in ipairs(fields) do
root_fields[i] = f.name.value
end

local name = ""
if def.name and def.name.value then
name = def.name.value
end

ctx._graphql = {
name = name,
operation = def.operation,
root_fields = root_fields,
}

return ctx._graphql
end


do
Expand Down Expand Up @@ -84,6 +160,11 @@ do
key = re_gsub(key, "-", "_", "jo")
val = get_var(key, t._request)

elseif core_str.has_prefix(key, "graphql_") then
-- trim the "graphql_" prefix
key = sub_str(key, 9)
membphis marked this conversation as resolved.
Show resolved Hide resolved
val = get_parsed_graphql(t)[key]

elseif key == "route_id" then
val = ngx.ctx.api_ctx and ngx.ctx.api_ctx.route_id

Expand Down
4 changes: 4 additions & 0 deletions conf/config-default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ apisix:
key_encrypt_salt: "edd1c9f0985e76a2" # If not set, will save origin ssl key into etcd.
# If set this, must be a string of length 16. And it will encrypt ssl key with AES-128-CBC
# !!! So do not change it after saving your ssl, it can't decrypt the ssl keys have be saved if you change !!

nginx_config: # config for render the template to genarate nginx.conf
error_log: "logs/error.log"
error_log_level: "warn" # warn,error
Expand Down Expand Up @@ -195,6 +196,9 @@ etcd:
# send: 2000 # default 2000ms
# read: 5000 # default 5000ms

graphql:
max_size: 1048576 # the maximum size limitation of graphql in bytes, defualt 1MiB

plugins: # plugin list (sorted in alphabetical order)
- api-breaker
- authz-keycloak
Expand Down
55 changes: 55 additions & 0 deletions doc/router-radixtree.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,58 @@ $ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f
```

This route will require the request header `host` equal `iresty.com`, request cookie key `_device_id` equal `a66f0cdc4ba2df8c096f74c9110163a9` etc.

### How to filter route by graphql attributes

APISIX supports filtering route by some attributes of graphql. Currently we support:

* graphql_operation
* graphql_name
* graphql_root_fields

For instance, with graphql like this:
```graphql
query getRepo {
owner {
name
}
repo {
created
}
}
```

* The `graphql_operation` is `query`
* The `graphql_name` is `getRepo`,
* The `graphql_root_fields` is `["owner", "repo"]`

We can filter such route out with:
```shell
$ curl http://127.0.0.1:9080/apisix/admin/routes/1 -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' -X PUT -i -d '
{
"methods": ["POST"],
"uri": "/_graphql",
"vars": [
["graphql_operation", "==", "query"],
["graphql_name", "==", "getRepo"],
["graphql_root_fields", "has", "owner"]
],
"upstream": {
"type": "roundrobin",
"nodes": {
"39.97.63.215:80": 1
}
}
}'
```

To prevent spending too much time reading invalid graphql request body, we only read the first 1 MiB
data from the request body. This limitation is configured via:

```yaml
graphql:
max_size: 1048576

```

If you need to pass a graphql body which is larger than the limitation, you can increase the value in `conf/config.yaml`.
1 change: 1 addition & 0 deletions rockspec/apisix-master-0.rockspec
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ dependencies = {
"dkjson = 2.5-2",
"resty-redis-cluster = 1.02-4",
"lua-resty-expr = 1.0.0",
"graphql = 0.0.2",
}

build = {
Expand Down
12 changes: 7 additions & 5 deletions t/APISIX.pm
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,11 @@ add_block_preprocessor(sub {
my ($block) = @_;
my $wait_etcd_sync = $block->wait_etcd_sync // 0.1;

my $lua_deps_path = <<_EOC_;
lua_package_path "$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;;";
lua_package_cpath "$apisix_home/?.so;$apisix_home/deps/lib/lua/5.1/?.so;$apisix_home/deps/lib64/lua/5.1/?.so;;";
_EOC_

my $main_config = $block->main_config // <<_EOC_;
worker_rlimit_core 500M;
env ENABLE_ETCD_AUTH;
Expand All @@ -150,9 +155,7 @@ _EOC_

my $stream_enable = $block->stream_enable;
my $stream_config = $block->stream_config // <<_EOC_;
lua_package_path "$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;;";
lua_package_cpath "$apisix_home/?.so;$apisix_home/deps/lib/lua/5.1/?.so;$apisix_home/deps/lib64/lua/5.1/?.so;;";

$lua_deps_path
lua_socket_log_errors off;

lua_shared_dict lrucache-lock-stream 10m;
Expand Down Expand Up @@ -232,8 +235,7 @@ _EOC_

my $http_config = $block->http_config // '';
$http_config .= <<_EOC_;
lua_package_path "$apisix_home/?.lua;$apisix_home/?/init.lua;$apisix_home/deps/share/lua/5.1/?.lua;$apisix_home/apisix/?.lua;$apisix_home/t/?.lua;;";
lua_package_cpath "$apisix_home/?.so;$apisix_home/deps/lib/lua/5.1/?.so;$apisix_home/deps/lib64/lua/5.1/?.so;;";
$lua_deps_path

lua_shared_dict plugin-limit-req 10m;
lua_shared_dict plugin-limit-count 10m;
Expand Down
5 changes: 3 additions & 2 deletions t/lib/test_admin.lua
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,15 @@ function _M.comp_tab(left_tab, right_tab)
local err
dir_names = {}

local _
if type(left_tab) == "string" then
left_tab, err = json.decode(left_tab)
left_tab, _, err = json.decode(left_tab)
membphis marked this conversation as resolved.
Show resolved Hide resolved
if not left_tab then
return false, "failed to decode expected data: " .. err
end
end
if type(right_tab) == "string" then
right_tab, err = json.decode(right_tab)
right_tab, _, err = json.decode(right_tab)
if not right_tab then
return false, "failed to decode expected data: " .. err
end
Expand Down
22 changes: 10 additions & 12 deletions t/plugin/openid-connect.t
Original file line number Diff line number Diff line change
Expand Up @@ -283,8 +283,7 @@ WWW-Authenticate: Bearer realm=apisix
local t = require("lib.test_admin").test
local code, body = t('/apisix/admin/routes/1',
ngx.HTTP_PUT,
[[{
"plugins": {
[[{ "plugins": {
"openid-connect": {
"client_id": "kbyuFDidLLm280LIwVFiazOqjO3ty8KH",
"client_secret": "60Op4HFM0I8ajz0WdiStAbziZ-VFQttXuxixHHs2R7r7-CW8GR79l-mmLqMhc-Sa",
Expand All @@ -294,10 +293,10 @@ WWW-Authenticate: Bearer realm=apisix
"timeout": 10,
"bearer_only": true,
"scope": "apisix",
"public_key": "-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ
hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==
-----END PUBLIC KEY-----",
"public_key": "-----BEGIN PUBLIC KEY-----\n]] ..
[[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] ..
[[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] ..
[[-----END PUBLIC KEY-----",
"token_signing_alg_values_expected": "RS256"
}
},
Expand All @@ -309,8 +308,7 @@ hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==
},
"uri": "/hello"
}]],
[[{
"node": {
[[{ "node": {
"value": {
"plugins": {
"openid-connect": {
Expand All @@ -322,10 +320,10 @@ hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==
"timeout": 10000,
"bearer_only": true,
"scope": "apisix",
"public_key": "-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ
hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==
-----END PUBLIC KEY-----",
"public_key": "-----BEGIN PUBLIC KEY-----\n]] ..
[[MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANW16kX5SMrMa2t7F2R1w6Bk/qpjS4QQ\n]] ..
[[hnrbED3Dpsl9JXAx90MYsIWp51hBxJSE/EPVK8WF/sjHK1xQbEuDfEECAwEAAQ==\n]] ..
[[-----END PUBLIC KEY-----",
"token_signing_alg_values_expected": "RS256"
}
},
Expand Down
Loading