This is an automated email from the ASF dual-hosted git repository.
Baoyuantop pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new cb133820c docs: update body-transformer and degraphql plugin docs
(#13295)
cb133820c is described below
commit cb133820c5ab795303f35d01f451b051b47990b7
Author: Yilia Lin <[email protected]>
AuthorDate: Tue Apr 28 15:22:48 2026 +0800
docs: update body-transformer and degraphql plugin docs (#13295)
---
docs/en/latest/plugins/body-transformer.md | 324 ++++++++++++++++++++++++++-
docs/en/latest/plugins/degraphql.md | 335 ++++++++-------------------
docs/zh/latest/config.json | 1 +
docs/zh/latest/plugins/body-transformer.md | 348 +++++++++++++++++++++++++++--
docs/zh/latest/plugins/degraphql.md | 196 ++++++++++++++++
5 files changed, 952 insertions(+), 252 deletions(-)
diff --git a/docs/en/latest/plugins/body-transformer.md
b/docs/en/latest/plugins/body-transformer.md
index 7e433bee1..3fde732ab 100644
--- a/docs/en/latest/plugins/body-transformer.md
+++ b/docs/en/latest/plugins/body-transformer.md
@@ -45,7 +45,7 @@ The `body-transformer` Plugin performs template-based
transformations to transfo
| `request.template` | string | True | | | Request body
transformation template. The template uses
[lua-resty-template](https://github.com/bungle/lua-resty-template) syntax. See
the [template
syntax](https://github.com/bungle/lua-resty-template#template-syntax) for more
details. You can also use auxiliary functions `_escape_json()` and
`_escape_xml()` to escape special characters such as double quotes, `_body` to
access request body, and `_ctx` to access context variables. |
| `request.template_is_base64` | boolean | False | false | | Set
to true if the template is base64 encoded. |
| `response` | object | False | | | Response body
transformation configuration. |
-| `response.input_format` | string | False | | [`xml`,`json`]
| Response body original media type. If unspecified, the value would be
determined by the `Content-Type` header to apply the corresponding decoder. If
the media type is neither `xml` nor `json`, the value would be left unset and
the transformation template will be directly applied. |
+| `response.input_format` | string | False | |
[`xml`,`json`,`encoded`,`args`,`plain`,`multipart`] | Response body original
media type. If unspecified, the decoder is determined by the `Content-Type`
header where applicable: `xml` for `text/xml`, `json` for `application/json`,
`encoded` for `application/x-www-form-urlencoded`, and `multipart` for any
`multipart/*` content type. The `args` option reads URI query args from the
request rather than decoding the response body. [...]
| `response.template` | string | True | | | Response body
transformation template. |
| `response.template_is_base64` | boolean | False | false | |
Set to true if the template is base64 encoded. |
@@ -607,3 +607,325 @@ You should see a response similar to the following:
...
}
```
+
+### Transform Response Body Based on Consumer Identity
+
+The following example demonstrates how to customize response body
transformations based on different Consumer identities. The example shows how
to return different response formats to different Consumers while filtering
sensitive fields and renaming properties.
+
+Create the response transformation template that applies different
transformations based on the Consumer identity:
+
+```shell
+rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr
-d '\r\n'
+{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
+{% if consumer_name == "consumer-a" then %}
+{
+ "user_id": {* user_id *},
+ "display_name": {* _escape_json(username) *},
+ "email": {* _escape_json(email) *}
+}
+{% elseif consumer_name == "consumer-b" then %}
+{
+ "user_id": {* user_id *},
+ "email": {* _escape_json(email) *},
+ "balance": {* balance *}
+}
+{% else %}
+{* _body *}
+{% end %}
+EOF
+)
+```
+
+Create three Consumers with `key-auth` configured:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-a",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-a"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-b",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-b"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-c",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-c"
+ }
+ }
+ }'
+```
+
+Create a Route with `body-transformer`, `key-auth`, and `mocking` Plugins:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "body-transformer-route",
+ "uri": "/mock",
+ "plugins": {
+ "key-auth": {},
+ "mocking": {
+ "response_example":
"{\"user_id\":1001,\"username\":\"john_doe\",\"email\":\"[email protected]\",\"phone\":\"+1-555-0123\",\"balance\":1250.50}"
+ },
+ "body-transformer": {
+ "response": {
+ "input_format": "json",
+ "template": "'"$rsp_template"'"
+ }
+ }
+ }
+ }'
+```
+
+Send requests with different `apikey` headers to verify the response
transformations.
+
+Send a request as `consumer-a`:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-a"
+```
+
+You should see a response similar to the following, which demonstrates these
transformations:
+
+- The `username` field has been renamed to `display_name`
+- The sensitive `phone` and `balance` fields have been filtered out
+
+```json
+{
+ "user_id": 1001,
+ "display_name": "john_doe",
+ "email": "[email protected]"
+}
+```
+
+Send a request as `consumer-b`:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-b"
+```
+
+You should see a response similar to the following, which demonstrates these
transformations:
+
+- The `username` and `phone` fields have been filtered out
+- The `balance` field has been preserved
+
+```json
+{
+ "user_id": 1001,
+ "email": "[email protected]",
+ "balance": 1250.50
+}
+```
+
+Send a request as `consumer-c`:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-c"
+```
+
+You should see a response similar to the following, which shows that the
original response is returned unchanged:
+
+```json
+{
+ "user_id": 1001,
+ "username": "john_doe",
+ "email": "[email protected]",
+ "phone": "+1-555-0123",
+ "balance": 1250.50
+}
+```
+
+### Transform Nested Response Body Based on Consumer Identity
+
+The following example demonstrates how to customize response body
transformations based on different Consumer identities. The example shows how
to extract nested fields, reorganize data structures, and flatten nested
objects while providing different response formats to different Consumers based
on their identity.
+
+Create the response transformation template that extracts and reorganizes
nested JSON fields based on the Consumer identity:
+
+```shell
+rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr
-d '\r\n'
+{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
+{% if consumer_name == "consumer-a" then %}
+{
+ "user_id": {* id *},
+ "user_name": {* _escape_json(name) *},
+ "email": {* _escape_json(profile.email) *},
+ "location": {
+ "city": {* _escape_json(profile.address.city) *},
+ "country": {* _escape_json(profile.address.country) *}
+ },
+ "created_at": {* _escape_json(metadata.created_at) *}
+}
+{% elseif consumer_name == "consumer-b" then %}
+{
+ "id": {* id *},
+ "name": {* _escape_json(name) *},
+ "status": {* _escape_json(status) *},
+ "profile": {
+ "email": {* _escape_json(profile.email) *},
+ "address": {
+ "city": {* _escape_json(profile.address.city) *}
+ }
+ }
+}
+{% else %}
+{* _body *}
+{% end %}
+EOF
+)
+```
+
+Create Consumers with `key-auth` configured:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-a",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-a"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-b",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-b"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-c",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-c"
+ }
+ }
+ }'
+```
+
+Create a Route with `body-transformer`, `key-auth`, and `mocking` Plugins
using the nested structure template:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "body-transformer-route",
+ "uri": "/mock",
+ "plugins": {
+ "key-auth": {},
+ "mocking": {
+ "response_example": "{\"id\":123,\"name\":\"John
Doe\",\"status\":\"active\",\"profile\":{\"email\":\"[email protected]\",\"address\":{\"city\":\"New
York\",\"country\":\"USA\"}},\"metadata\":{\"created_at\":\"2024-01-01\",\"tags\":[\"vip\",\"premium\"]}}"
+ },
+ "body-transformer": {
+ "response": {
+ "input_format": "json",
+ "template": "'"$rsp_template"'"
+ }
+ }
+ }
+ }'
+```
+
+Send a request as `consumer-a` to verify the nested structure transformation:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-a"
+```
+
+You should see a response similar to the following, which demonstrates these
nested field transformations:
+
+- `profile.email` has been extracted to top-level `email`
+- `profile.address.city` and `profile.address.country` have been combined into
a new `location` object
+- `metadata.created_at` has been extracted to top-level `created_at`
+
+```json
+{
+ "user_id": 123,
+ "user_name": "John Doe",
+ "email": "[email protected]",
+ "location": {
+ "city": "New York",
+ "country": "USA"
+ },
+ "created_at": "2024-01-01"
+}
+```
+
+Send a request as `consumer-b` to verify the nested structure transformation:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-b"
+```
+
+You should see a response similar to the following, which demonstrates these
transformations:
+
+- The original `profile` object structure has been preserved
+- `profile.address.country` and `metadata` fields have been filtered out
+
+```json
+{
+ "id": 123,
+ "name": "John Doe",
+ "status": "active",
+ "profile": {
+ "email": "[email protected]",
+ "address": {
+ "city": "New York"
+ }
+ }
+}
+```
+
+Send a request as `consumer-c` to verify the nested structure transformation:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-c"
+```
+
+You should see a response similar to the following, which shows that the
original nested response is returned unchanged:
+
+```json
+{
+ "id": 123,
+ "name": "John Doe",
+ "status": "active",
+ "profile": {
+ "email": "[email protected]",
+ "address": {
+ "city": "New York",
+ "country": "USA"
+ }
+ },
+ "metadata": {
+ "created_at": "2024-01-01",
+ "tags": ["vip", "premium"]
+ }
+}
+```
diff --git a/docs/en/latest/plugins/degraphql.md
b/docs/en/latest/plugins/degraphql.md
index 36e90703e..bd0d55ca8 100644
--- a/docs/en/latest/plugins/degraphql.md
+++ b/docs/en/latest/plugins/degraphql.md
@@ -1,11 +1,11 @@
---
-title: GraphQL to REST (degraphql)
+title: degraphql
keywords:
- Apache APISIX
- API Gateway
- Plugin
- - Degraphql
-description: This document contains information about the Apache APISIX
degraphql Plugin.
+ - degraphql
+description: The degraphql Plugin enables communication with upstream GraphQL
services through standard HTTP requests by mapping GraphQL queries to HTTP
endpoints, simplifying API integration.
---
<!--
@@ -27,311 +27,170 @@ description: This document contains information about the
Apache APISIX degraphq
#
-->
+<head>
+ <link rel="canonical" href="https://docs.api7.ai/hub/degraphql" />
+</head>
+
## Description
-The `degraphql` Plugin is used to support decoding RESTful API to GraphQL.
+The `degraphql` Plugin supports communicating with upstream GraphQL services
over regular HTTP requests by mapping GraphQL queries to HTTP endpoints.
## Attributes
-| Name | Type | Required | Description
|
-| -------------- | ------ | -------- |
--------------------------------------------------------------------------------------------
|
-| query | string | True | The GraphQL query sent to the upstream
|
-| operation_name | string | False | The name of the operation, is only
required if multiple operations are present in the query. |
-| variables | array | False | The variables used in the GraphQL query
|
+| Name | Type | Required | Description
|
+| ---------------- | ------------ | -------- |
--------------------------------------------------------------------------------------------------
|
+| `query` | string | True | The GraphQL query sent to the
Upstream. |
+| `operation_name` | string | False | The name of the operation, only
required if multiple operations are present in the query. |
+| `variables` | array[string]| False | The names of variables used in
the GraphQL query, extracted from the request body or query string. |
+
+## Examples
-## Example usage
+The examples below demonstrate how you can configure `degraphql` for different
scenarios.
-### Start GraphQL server
+:::note
-We use docker to deploy a [GraphQL server
demo](https://github.com/npalm/graphql-java-demo) as the backend.
+You can fetch the `admin_key` from `config.yaml` and save to an environment
variable with the following command:
```bash
-docker run -d --name grapql-demo -p 8080:8080 npalm/graphql-java-demo
+admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed
's/"//g')
```
-After starting the server, the following endpoints are now available:
-
-- http://localhost:8080/graphiql - GraphQL IDE - GrahphiQL
-- http://localhost:8080/playground - GraphQL IDE - Prisma GraphQL Client
-- http://localhost:8080/altair - GraphQL IDE - Altair GraphQL Client
-- http://localhost:8080/ - A simple reacter
-- ws://localhost:8080/subscriptions
+:::
-### Enable Plugin
+The examples below use [Pokemon GraphQL API](https://graphql-pokemon.js.org/)
as the upstream GraphQL server.
-#### Query list
+### Transform a Basic Query
-If we have a GraphQL query like this:
+The following example demonstrates how to transform a simple GraphQL query:
```graphql
query {
- persons {
- id
- name
- }
-}
-```
-
-We can execute it on `http://localhost:8080/playground`, and get the data as
below:
-
-```json
-{
- "data": {
- "persons": [
- {
- "id": "7",
- "name": "Niek"
- },
- {
- "id": "8",
- "name": "Josh"
- },
- ......
- ]
+ getAllPokemon {
+ key
+ color
}
}
```
-Now we can use RESTful API to query the same data that is proxy by APISIX.
-
-First, we need to create a route in APISIX, and enable the degreaph plugin on
the route, we need to define the GraphQL query in the plugin's config.
+Create a Route with the `degraphql` Plugin as follows:
-```bash
-curl --location --request PUT 'http://localhost:9180/apisix/admin/routes/1' \
---header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
---header 'Content-Type: application/json' \
---data-raw '{
- "uri": "/graphql",
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "degraphql-route",
+ "methods": ["POST"],
+ "uri": "/v8",
"upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:8080": 1
- }
+ "type": "roundrobin",
+ "nodes": {
+ "graphqlpokemon.favware.tech": 1
+ },
+ "scheme": "https",
+ "pass_host": "node"
},
"plugins": {
- "degraphql": {
- "query": "{\n persons {\n id\n name\n }\n}\n"
- }
+ "degraphql": {
+ "query": "{\n getAllPokemon {\n key\n color\n }\n}"
+ }
}
-}'
+ }'
```
-We convert the GraphQL query
+Send a request to the Route to verify:
-```graphql
-{
- persons {
- id
- name
- }
-}
-```
-
-to JSON string `"{\n persons {\n id\n name\n }\n}\n"`, and put it in
the plugin's configuration.
-
-Then we can query the data by RESTful API:
-
-```bash
-curl --location --request POST 'http://localhost:9080/graphql'
+```shell
+curl "http://127.0.0.1:9080/v8" -X POST
```
-and get the result:
+You should see a response similar to the following:
```json
{
"data": {
- "persons": [
- {
- "id": "7",
- "name": "Niek"
- },
- {
- "id": "8",
- "name": "Josh"
- },
- ......
+ "getAllPokemon": [
+ { "key": "pokestarsmeargle", "color": "White" },
+ { "key": "pokestarufo", "color": "White" },
+ { "key": "pokestarufo2", "color": "White" },
+ ...
+ { "key": "terapagosstellar", "color": "Blue" },
+ { "key": "pecharunt", "color": "Purple" }
]
}
}
```
-#### Query with variables
+### Transform a Query with Variables
-If we have a GraphQL query like this:
+The following example demonstrates how to transform a GraphQL query that uses
a variable:
```graphql
-query($name: String!, $githubAccount: String!) {
- persons(filter: { name: $name, githubAccount: $githubAccount }) {
- id
- name
- blog
- githubAccount
- talks {
- id
- title
- }
+query ($pokemon: PokemonEnum!) {
+ getPokemon(
+ pokemon: $pokemon
+ ) {
+ color
+ species
}
}
variables:
{
- "name": "Niek",
- "githubAccount": "npalm"
+ "pokemon": "pikachu"
}
```
-we can execute it on `http://localhost:8080/playground`, and get the data as
below:
+Create a Route with the `degraphql` Plugin as follows:
-```json
-{
- "data": {
- "persons": [
- {
- "id": "7",
- "name": "Niek",
- "blog": "https://040code.github.io",
- "githubAccount": "npalm",
- "talks": [
- {
- "id": "19",
- "title": "GraphQL - The Next API Language"
- },
- {
- "id": "20",
- "title": "Immutable Infrastructure"
- }
- ]
- }
- ]
- }
-}
-```
-
-We convert the GraphQL query to JSON string like `"query($name: String!,
$githubAccount: String!) {\n persons(filter: { name: $name, githubAccount:
$githubAccount }) {\n id\n name\n blog\n githubAccount\n talks
{\n id\n title\n }\n }\n}"`, so we create a route like this:
-
-```bash
-curl --location --request PUT 'http://localhost:9180/apisix/admin/routes/1' \
---header 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1' \
---header 'Content-Type: application/json' \
---data-raw '{
- "uri": "/graphql",
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "degraphql-route",
+ "uri": "/v8",
"upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:8080": 1
- }
+ "type": "roundrobin",
+ "nodes": {
+ "graphqlpokemon.favware.tech": 1
+ },
+ "scheme": "https",
+ "pass_host": "node"
},
"plugins": {
- "degraphql": {
- "query": "query($name: String!, $githubAccount: String!) {\n
persons(filter: { name: $name, githubAccount: $githubAccount }) {\n id\n
name\n blog\n githubAccount\n talks {\n id\n title\n }\n
}\n}",
- "variables": [
- "name",
- "githubAccount"
- ]
- }
+ "degraphql": {
+ "query": "query ($pokemon: PokemonEnum!) {\n getPokemon(\n
pokemon: $pokemon\n ) {\n color\n species\n }\n}\n",
+ "variables": ["pokemon"]
+ }
}
-}'
+ }'
```
-We define the `variables` in the plugin's config, and the `variables` is an
array, which contains the variables' name in the GraphQL query, so that we can
pass the query variables by RESTful API.
-
-Query the data by RESTful API that proxy by APISIX:
-
-```bash
-curl --location --request POST 'http://localhost:9080/graphql' \
---header 'Content-Type: application/json' \
---data-raw '{
- "name": "Niek",
- "githubAccount": "npalm"
-}'
-```
+Send a POST request to the Route with the variable in the request body:
-and get the result:
-
-```json
-{
- "data": {
- "persons": [
- {
- "id": "7",
- "name": "Niek",
- "blog": "https://040code.github.io",
- "githubAccount": "npalm",
- "talks": [
- {
- "id": "19",
- "title": "GraphQL - The Next API Language"
- },
- {
- "id": "20",
- "title": "Immutable Infrastructure"
- }
- ]
- }
- ]
- }
-}
+```shell
+curl "http://127.0.0.1:9080/v8" -X POST \
+ -d '{
+ "pokemon": "pikachu"
+ }'
```
-which is the same as the result of the GraphQL query.
-
-It's also possible to get the same result via GET request:
-
-```bash
-curl 'http://localhost:9080/graphql?name=Niek&githubAccount=npalm'
-```
+You should see a response similar to the following:
```json
{
"data": {
- "persons": [
- {
- "id": "7",
- "name": "Niek",
- "blog": "https://040code.github.io",
- "githubAccount": "npalm",
- "talks": [
- {
- "id": "19",
- "title": "GraphQL - The Next API Language"
- },
- {
- "id": "20",
- "title": "Immutable Infrastructure"
- }
- ]
- }
- ]
+ "getPokemon": {
+ "color": "Yellow",
+ "species": "pikachu"
+ }
}
}
```
-In the GET request, the variables are passed in the query string.
-
-## Delete Plugin
-
-To remove the `degraphql` Plugin, you can delete the corresponding JSON
configuration from the Plugin configuration. APISIX will automatically reload
and you do not have to restart for this to take effect.
-
-:::note
-You can fetch the `admin_key` from `config.yaml` and save to an environment
variable with the following command:
-
-```bash
-admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed
's/"//g')
-```
-
-:::
+Alternatively, you can also pass the variable in the URL query string of a GET
request:
```shell
-curl http://127.0.0.1:9180/apisix/admin/routes/1 -H "X-API-KEY: $admin_key"
-X PUT -d '
-{
- "methods": ["GET"],
- "uri": "/graphql",
- "plugins": {},
- "upstream": {
- "type": "roundrobin",
- "nodes": {
- "127.0.0.1:8080": 1
- }
- }
-}'
+curl "http://127.0.0.1:9080/v8?pokemon=pikachu"
```
+
+You should see the same response as the previous.
diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json
index 1ab36fa2d..b765065d3 100644
--- a/docs/zh/latest/config.json
+++ b/docs/zh/latest/config.json
@@ -101,6 +101,7 @@
"plugins/grpc-web",
"plugins/fault-injection",
"plugins/mocking",
+ "plugins/degraphql",
"plugins/body-transformer",
"plugins/attach-consumer-label"
]
diff --git a/docs/zh/latest/plugins/body-transformer.md
b/docs/zh/latest/plugins/body-transformer.md
index 701efa410..d1d42f150 100644
--- a/docs/zh/latest/plugins/body-transformer.md
+++ b/docs/zh/latest/plugins/body-transformer.md
@@ -42,10 +42,10 @@ description: body-transformer 插件执行基于模板的转换,将请求和/
|--------------|----------------------|-------|---------------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------|
| `request` | object | 否 | | | 请求体转换配置。 |
| `request.input_format` | string | 否 | |
[`xml`,`json`,`encoded`,`args`,`plain`,`multipart`] | 请求体原始媒体类型。若未指定,则该值将由
`Content-Type` 标头确定以应用相应的解码器。`xml` 选项对应于 `text/xml` 媒体类型。`json` 选项对应于
`application/json` 媒体类型。`encoded` 选项对应于 `application/x-www-form-urlencoded`
媒体类型。`args` 选项对应于 GET 请求。`plain` 选项对应于 `text/plain` 媒体类型。`multipart` 选项对应于
`multipart/related` 媒体类型。如果媒体类型不是这两种类型,则该值将保留未设置状态并直接应用转换模板。 |
-| `request.template` | string | True | | | 请求体转换模板。模板使用
[lua-resty-template](https://github.com/bungle/lua-resty-template)
语法。有关更多详细信息,请参阅
[模板语法](https://github.com/bungle/lua-resty-template#template-syntax)。您还可以使用辅助函数
`_escape_json()` 和 `_escape_xml()` 转义双引号等特殊字符,使用 `_body` 访问请求正文,使用 `_ctx`
访问上下文变量。|
+| `request.template` | string | True | | | 请求体转换模板。模板使用
[lua-resty-template](https://github.com/bungle/lua-resty-template)
语法。有关更多详细信息,请参阅
[模板语法](https://github.com/bungle/lua-resty-template#template-syntax)。你还可以使用辅助函数
`_escape_json()` 和 `_escape_xml()` 转义双引号等特殊字符,使用 `_body` 访问请求正文,使用 `_ctx`
访问上下文变量。|
| `request.template_is_base64` | boolean | 否 | false | | 如果模板是 base64 编码的,则设置为
true。|
| `response` | object | 否 | | | 响应体转换配置。|
-| `response.input_format` | string | 否 | | [`xml`,`json`] |
响应体原始媒体类型。如果未指定,则该值将由 `Content-Type` 标头确定以应用相应的解码器。如果媒体类型既不是 `xml` 也不是
`json`,则该值将保留未设置状态,并直接应用转换模板。|
+| `response.input_format` | string | 否 | |
[`xml`,`json`,`encoded`,`args`,`plain`,`multipart`] |
响应体原始媒体类型。若未指定,则在可识别的情况下会根据 `Content-Type` 标头自动确定解码器:`xml` 对应 `text/xml`,`json`
对应 `application/json`,`encoded` 对应
`application/x-www-form-urlencoded`,`multipart` 对应任意 `multipart/*` 内容类型。`args`
选项表示读取请求的 URI query args,而非解码响应体。`plain` 选项需显式指定,不会从 `Content-Type: text/plain`
自动识别。如果媒体类型不属于上述任一受支持的类型,则该值将保留未设置状态并直接应用转换模板。|
| `response.template` | string | True | | | 响应主体转换模板。|
| `response.template_is_base64` | boolean | 否 | false | | 如果模板是 base64
编码的,则设置为 true。|
@@ -55,7 +55,7 @@ description: body-transformer 插件执行基于模板的转换,将请求和/
:::note
-您可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量:
+你可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量:
```bash
admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed
's/"//g')
@@ -65,9 +65,9 @@ admin_key=$(yq '.deployment.admin.admin_key[0].key'
conf/config.yaml | sed 's/"/
转换模板使用 [lua-resty-template](https://github.com/bungle/lua-resty-template)
语法。请参阅 [模板语法](https://github.com/bungle/lua-resty-template#template-syntax)
了解更多信息。
-您还可以使用辅助函数 `_escape_json()` 和 `_escape_xml()` 转义特殊字符(例如双引号)、`_body` 访问请求正文以及
`_ctx` 访问上下文变量。
+你还可以使用辅助函数 `_escape_json()` 和 `_escape_xml()` 转义特殊字符(例如双引号)、`_body` 访问请求正文以及
`_ctx` 访问上下文变量。
-在所有情况下,您都应确保转换模板是有效的 JSON 字符串。
+在所有情况下,你都应确保转换模板是有效的 JSON 字符串。
### JSON 和 XML SOAP 之间的转换
@@ -183,7 +183,7 @@ curl "http://127.0.0.1:9080/ws" -X POST -d '{"name":
"Spain"}'
请求中发送的 JSON 主体将在转发到上游 SOAP 服务之前转换为 XML,响应主体将从 XML 转换回 JSON。
-您应该会看到类似以下内容的响应:
+你应该会看到类似以下内容的响应:
```json
{
@@ -232,7 +232,7 @@ curl "http://127.0.0.1:9080/anything" -X POST \
-i
```
-您应该看到以下响应:
+你应该看到以下响应:
```json
{
@@ -282,7 +282,7 @@ curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
curl -i "http://127.0.0.1:9080/anything?name=hello"
```
-您应该看到如下响应:
+你应该看到如下响应:
```json
{
@@ -352,7 +352,7 @@ curl "http://127.0.0.1:9080/anything" -X POST \
-i
```
-您应该会看到类似以下内容的响应,这验证了 YAML 主体已适当地转换为 JSON:
+你应该会看到类似以下内容的响应,这验证了 YAML 主体已适当地转换为 JSON:
```json
{
@@ -403,7 +403,7 @@ curl "http://127.0.0.1:9080/anything" -X POST \
-d 'name=hello&age=20'
```
-您应该会看到类似以下内容的响应:
+你应该会看到类似以下内容的响应:
```json
{
@@ -455,7 +455,7 @@ curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
curl "http://127.0.0.1:9080/anything?name=john"
```
-您应该会看到类似以下内容的响应:
+你应该会看到类似以下内容的响应:
```json
{
@@ -511,7 +511,7 @@ curl "http://127.0.0.1:9080/anything" -X POST \
-i
```
-您应该会看到类似以下内容的响应:
+你应该会看到类似以下内容的响应:
```json
{
@@ -586,7 +586,7 @@ curl -X POST \
"http://127.0.0.1:9080/anything"
```
-您应该会看到类似以下内容的响应:
+你应该会看到类似以下内容的响应:
```json
{
@@ -607,3 +607,325 @@ curl -X POST \
...
}
```
+
+### 根据消费者身份转换响应体
+
+以下示例演示如何根据不同消费者身份自定义响应体转换。该示例展示如何向不同消费者返回不同的响应格式,同时过滤敏感字段和重命名属性。
+
+创建根据消费者身份应用不同转换的响应转换模板:
+
+```shell
+rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr
-d '\r\n'
+{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
+{% if consumer_name == "consumer-a" then %}
+{
+ "user_id": {* user_id *},
+ "display_name": {* _escape_json(username) *},
+ "email": {* _escape_json(email) *}
+}
+{% elseif consumer_name == "consumer-b" then %}
+{
+ "user_id": {* user_id *},
+ "email": {* _escape_json(email) *},
+ "balance": {* balance *}
+}
+{% else %}
+{* _body *}
+{% end %}
+EOF
+)
+```
+
+创建三个配置了 `key-auth` 的消费者:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-a",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-a"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-b",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-b"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-c",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-c"
+ }
+ }
+ }'
+```
+
+创建一个配置了 `body-transformer`、`key-auth` 和 `mocking` 插件的路由:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "body-transformer-route",
+ "uri": "/mock",
+ "plugins": {
+ "key-auth": {},
+ "mocking": {
+ "response_example":
"{\"user_id\":1001,\"username\":\"john_doe\",\"email\":\"[email protected]\",\"phone\":\"+1-555-0123\",\"balance\":1250.50}"
+ },
+ "body-transformer": {
+ "response": {
+ "input_format": "json",
+ "template": "'"$rsp_template"'"
+ }
+ }
+ }
+ }'
+```
+
+使用不同的 `apikey` 请求头发送请求以验证响应转换。
+
+以 `consumer-a` 身份发送请求:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-a"
+```
+
+你应该会看到类似以下内容的响应,其中演示了以下转换:
+
+- `username` 字段已重命名为 `display_name`
+- 敏感的 `phone` 和 `balance` 字段已被过滤
+
+```json
+{
+ "user_id": 1001,
+ "display_name": "john_doe",
+ "email": "[email protected]"
+}
+```
+
+以 `consumer-b` 身份发送请求:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-b"
+```
+
+你应该会看到类似以下内容的响应,其中演示了以下转换:
+
+- `username` 和 `phone` 字段已被过滤
+- `balance` 字段已被保留
+
+```json
+{
+ "user_id": 1001,
+ "email": "[email protected]",
+ "balance": 1250.50
+}
+```
+
+以 `consumer-c` 身份发送请求:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-c"
+```
+
+你应该会看到类似以下内容的响应,显示原始响应未经更改地返回:
+
+```json
+{
+ "user_id": 1001,
+ "username": "john_doe",
+ "email": "[email protected]",
+ "phone": "+1-555-0123",
+ "balance": 1250.50
+}
+```
+
+### 根据消费者身份转换嵌套响应体
+
+以下示例演示如何根据不同消费者身份自定义响应体转换。该示例展示如何提取嵌套字段、重新组织数据结构,并在向不同消费者提供不同响应格式的同时扁平化嵌套对象。
+
+创建根据消费者身份提取和重新组织嵌套 JSON 字段的响应转换模板:
+
+```shell
+rsp_template=$(cat <<EOF | awk '{gsub(/"/,"\\\"");};1' | awk '{$1=$1};1' | tr
-d '\r\n'
+{% local consumer_name = _ctx.consumer and _ctx.consumer.username or "" %}
+{% if consumer_name == "consumer-a" then %}
+{
+ "user_id": {* id *},
+ "user_name": {* _escape_json(name) *},
+ "email": {* _escape_json(profile.email) *},
+ "location": {
+ "city": {* _escape_json(profile.address.city) *},
+ "country": {* _escape_json(profile.address.country) *}
+ },
+ "created_at": {* _escape_json(metadata.created_at) *}
+}
+{% elseif consumer_name == "consumer-b" then %}
+{
+ "id": {* id *},
+ "name": {* _escape_json(name) *},
+ "status": {* _escape_json(status) *},
+ "profile": {
+ "email": {* _escape_json(profile.email) *},
+ "address": {
+ "city": {* _escape_json(profile.address.city) *}
+ }
+ }
+}
+{% else %}
+{* _body *}
+{% end %}
+EOF
+)
+```
+
+创建配置了 `key-auth` 的消费者:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-a",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-a"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-b",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-b"
+ }
+ }
+ }'
+
+curl "http://127.0.0.1:9180/apisix/admin/consumers" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "username": "consumer-c",
+ "plugins": {
+ "key-auth": {
+ "key": "consumer-c"
+ }
+ }
+ }'
+```
+
+使用嵌套结构模板创建配置了 `body-transformer`、`key-auth` 和 `mocking` 插件的路由:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "body-transformer-route",
+ "uri": "/mock",
+ "plugins": {
+ "key-auth": {},
+ "mocking": {
+ "response_example": "{\"id\":123,\"name\":\"John
Doe\",\"status\":\"active\",\"profile\":{\"email\":\"[email protected]\",\"address\":{\"city\":\"New
York\",\"country\":\"USA\"}},\"metadata\":{\"created_at\":\"2024-01-01\",\"tags\":[\"vip\",\"premium\"]}}"
+ },
+ "body-transformer": {
+ "response": {
+ "input_format": "json",
+ "template": "'"$rsp_template"'"
+ }
+ }
+ }
+ }'
+```
+
+以 `consumer-a` 身份发送请求以验证嵌套结构转换:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-a"
+```
+
+你应该会看到类似以下内容的响应,其中演示了以下嵌套字段转换:
+
+- `profile.email` 已被提取到顶层 `email`
+- `profile.address.city` 和 `profile.address.country` 已合并为新的 `location` 对象
+- `metadata.created_at` 已被提取到顶层 `created_at`
+
+```json
+{
+ "user_id": 123,
+ "user_name": "John Doe",
+ "email": "[email protected]",
+ "location": {
+ "city": "New York",
+ "country": "USA"
+ },
+ "created_at": "2024-01-01"
+}
+```
+
+以 `consumer-b` 身份发送请求以验证嵌套结构转换:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-b"
+```
+
+你应该会看到类似以下内容的响应,其中演示了以下转换:
+
+- 原始 `profile` 对象结构已被保留
+- `profile.address.country` 和 `metadata` 字段已被过滤
+
+```json
+{
+ "id": 123,
+ "name": "John Doe",
+ "status": "active",
+ "profile": {
+ "email": "[email protected]",
+ "address": {
+ "city": "New York"
+ }
+ }
+}
+```
+
+以 `consumer-c` 身份发送请求以验证嵌套结构转换:
+
+```shell
+curl "http://127.0.0.1:9080/mock" -H "apikey: consumer-c"
+```
+
+你应该会看到类似以下内容的响应,显示原始嵌套响应未经更改地返回:
+
+```json
+{
+ "id": 123,
+ "name": "John Doe",
+ "status": "active",
+ "profile": {
+ "email": "[email protected]",
+ "address": {
+ "city": "New York",
+ "country": "USA"
+ }
+ },
+ "metadata": {
+ "created_at": "2024-01-01",
+ "tags": ["vip", "premium"]
+ }
+}
+```
diff --git a/docs/zh/latest/plugins/degraphql.md
b/docs/zh/latest/plugins/degraphql.md
new file mode 100644
index 000000000..7e8824954
--- /dev/null
+++ b/docs/zh/latest/plugins/degraphql.md
@@ -0,0 +1,196 @@
+---
+title: degraphql
+keywords:
+ - Apache APISIX
+ - API 网关
+ - Plugin
+ - degraphql
+description: degraphql 插件通过将 GraphQL 查询映射到 HTTP 端点,支持通过标准 HTTP 请求与上游 GraphQL
服务进行通信,简化 API 集成。
+---
+
+<!--
+#
+# 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.
+#
+-->
+
+<head>
+ <link rel="canonical" href="https://docs.api7.ai/hub/degraphql" />
+</head>
+
+## 描述
+
+`degraphql` 插件支持通过将 GraphQL 查询映射到 HTTP 端点,使用普通 HTTP 请求与上游 GraphQL 服务进行通信。
+
+## 属性
+
+| 名称 | 类型 | 必选项 | 描述
|
+| ---------------- | -------------- | ------ |
-------------------------------------------------------------------------------------------
|
+| `query` | string | 是 | 发送到上游的 GraphQL 查询。
|
+| `operation_name` | string | 否 | 操作名称,仅在查询中存在多个操作时需要。
|
+| `variables` | array[string] | 否 | GraphQL
查询中使用的变量名称,从请求体或查询字符串中提取。 |
+
+## 示例
+
+以下示例演示了如何针对不同场景配置 `degraphql`。
+
+:::note
+
+你可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量:
+
+```bash
+admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed
's/"//g')
+```
+
+:::
+
+以下示例使用 [Pokemon GraphQL API](https://graphql-pokemon.js.org/) 作为上游 GraphQL 服务器。
+
+### 转换基本查询
+
+以下示例演示如何转换如下简单 GraphQL 查询:
+
+```graphql
+query {
+ getAllPokemon {
+ key
+ color
+ }
+}
+```
+
+按如下方式创建一个带有 `degraphql` 插件的路由:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "degraphql-route",
+ "methods": ["POST"],
+ "uri": "/v8",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "graphqlpokemon.favware.tech": 1
+ },
+ "scheme": "https",
+ "pass_host": "node"
+ },
+ "plugins": {
+ "degraphql": {
+ "query": "{\n getAllPokemon {\n key\n color\n }\n}"
+ }
+ }
+ }'
+```
+
+向路由发送请求以验证:
+
+```shell
+curl "http://127.0.0.1:9080/v8" -X POST
+```
+
+你应该会看到类似以下内容的响应:
+
+```json
+{
+ "data": {
+ "getAllPokemon": [
+ { "key": "pokestarsmeargle", "color": "White" },
+ { "key": "pokestarufo", "color": "White" },
+ { "key": "pokestarufo2", "color": "White" },
+ ...
+ { "key": "terapagosstellar", "color": "Blue" },
+ { "key": "pecharunt", "color": "Purple" }
+ ]
+ }
+}
+```
+
+### 转换带变量的查询
+
+以下示例演示如何转换带有变量的 GraphQL 查询:
+
+```graphql
+query ($pokemon: PokemonEnum!) {
+ getPokemon(
+ pokemon: $pokemon
+ ) {
+ color
+ species
+ }
+}
+
+variables:
+{
+ "pokemon": "pikachu"
+}
+```
+
+按如下方式创建一个带有 `degraphql` 插件的路由:
+
+```shell
+curl "http://127.0.0.1:9180/apisix/admin/routes" -X PUT \
+ -H "X-API-KEY: ${admin_key}" \
+ -d '{
+ "id": "degraphql-route",
+ "uri": "/v8",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "graphqlpokemon.favware.tech": 1
+ },
+ "scheme": "https",
+ "pass_host": "node"
+ },
+ "plugins": {
+ "degraphql": {
+ "query": "query ($pokemon: PokemonEnum!) {\n getPokemon(\n
pokemon: $pokemon\n ) {\n color\n species\n }\n}\n",
+ "variables": ["pokemon"]
+ }
+ }
+ }'
+```
+
+向路由发送 POST 请求,将变量放在请求体中:
+
+```shell
+curl "http://127.0.0.1:9080/v8" -X POST \
+ -d '{
+ "pokemon": "pikachu"
+ }'
+```
+
+你应该会看到类似以下内容的响应:
+
+```json
+{
+ "data": {
+ "getPokemon": {
+ "color": "Yellow",
+ "species": "pikachu"
+ }
+ }
+}
+```
+
+你也可以通过 GET 请求的 URL 查询字符串传递变量:
+
+```shell
+curl "http://127.0.0.1:9080/v8?pokemon=pikachu"
+```
+
+你应该会看到与上述相同的响应。