This is an automated email from the ASF dual-hosted git repository.
AlinsRan 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 0048226a4 feat: add proxy-buffering plugin (#13446)
0048226a4 is described below
commit 0048226a4e7aa4f2c3c6a0a776dfeafe748641af
Author: AlinsRan <[email protected]>
AuthorDate: Thu Jun 4 14:28:40 2026 +0800
feat: add proxy-buffering plugin (#13446)
---
.ignore_words | 1 +
apisix/cli/config.lua | 1 +
apisix/cli/ngx_tpl.lua | 40 ++++++++++
apisix/init.lua | 10 +++
apisix/plugins/proxy-buffering.lua | 53 +++++++++++++
ci/linux_apisix_current_luarocks_runner.sh | 2 +-
conf/config.yaml.example | 1 +
docs/en/latest/config.json | 1 +
docs/en/latest/plugins/proxy-buffering.md | 109 +++++++++++++++++++++++++++
docs/zh/latest/config.json | 1 +
docs/zh/latest/plugins/proxy-buffering.md | 117 +++++++++++++++++++++++++++++
t/APISIX.pm | 38 ++++++++++
t/admin/plugins.t | 1 +
t/certs/server.crt | 18 +++++
t/certs/server.key | 28 +++++++
t/certs/sse_server.crt | 19 +++++
t/certs/sse_server.key | 28 +++++++
t/cli/test_proxy_buffering.sh | 94 +++++++++++++++++++++++
t/cli/test_sse.py | 84 +++++++++++++++++++++
19 files changed, 645 insertions(+), 1 deletion(-)
diff --git a/.ignore_words b/.ignore_words
index 86683d38e..41c625dbc 100644
--- a/.ignore_words
+++ b/.ignore_words
@@ -9,3 +9,4 @@ nulll
smove
aks
nin
+bre
diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua
index 479712b37..664159a36 100644
--- a/apisix/cli/config.lua
+++ b/apisix/cli/config.lua
@@ -196,6 +196,7 @@ local _M = {
"real-ip",
"ai",
"client-control",
+ "proxy-buffering",
"proxy-control",
"request-id",
"zipkin",
diff --git a/apisix/cli/ngx_tpl.lua b/apisix/cli/ngx_tpl.lua
index f14a7c4fb..9603eae46 100644
--- a/apisix/cli/ngx_tpl.lua
+++ b/apisix/cli/ngx_tpl.lua
@@ -956,6 +956,46 @@ http {
}
{% end %}
+ {% if enabled_plugins["proxy-buffering"] then %}
+ location @disable_proxy_buffering {
+ access_by_lua_block {
+ apisix.disable_proxy_buffering_access_phase()
+ }
+
+ proxy_http_version 1.1;
+ proxy_set_header Host $upstream_host;
+ proxy_set_header Upgrade $upstream_upgrade;
+ proxy_set_header Connection $upstream_connection;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_pass_header Date;
+
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $var_x_forwarded_proto;
+ proxy_set_header X-Forwarded-Host $var_x_forwarded_host;
+ proxy_set_header X-Forwarded-Port $var_x_forwarded_port;
+
+ proxy_pass $upstream_scheme://apisix_backend$upstream_uri;
+
+ {% if enabled_plugins["proxy-mirror"] then %}
+ mirror /proxy_mirror;
+ {% end %}
+
+ header_filter_by_lua_block {
+ apisix.http_header_filter_phase()
+ }
+
+ body_filter_by_lua_block {
+ apisix.http_body_filter_phase()
+ }
+
+ log_by_lua_block {
+ apisix.http_log_phase()
+ }
+
+ proxy_buffering off;
+ }
+ {% end %}
+
{% if enabled_plugins["proxy-mirror"] then %}
location = /proxy_mirror {
internal;
diff --git a/apisix/init.lua b/apisix/init.lua
index a229fe857..01838da5b 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -610,6 +610,11 @@ function _M.handle_upstream(api_ctx, route,
enable_websocket)
stash_ngx_ctx()
return ngx.exec("@dubbo_pass")
end
+
+ if ngx.ctx.disable_proxy_buffering then
+ stash_ngx_ctx()
+ return ngx.exec("@disable_proxy_buffering")
+ end
end
@@ -873,6 +878,11 @@ function _M.dubbo_access_phase()
end
+function _M.disable_proxy_buffering_access_phase()
+ ngx.ctx = fetch_ctx()
+end
+
+
function _M.grpc_access_phase()
ngx.ctx = fetch_ctx()
diff --git a/apisix/plugins/proxy-buffering.lua
b/apisix/plugins/proxy-buffering.lua
new file mode 100644
index 000000000..5c903b7f0
--- /dev/null
+++ b/apisix/plugins/proxy-buffering.lua
@@ -0,0 +1,53 @@
+--
+-- 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 require = require
+local ngx = ngx
+local core = require("apisix.core")
+
+
+local schema = {
+ type = "object",
+ properties = {
+ disable_proxy_buffering = {
+ type = "boolean",
+ default = false,
+ },
+ },
+}
+
+
+local plugin_name = "proxy-buffering"
+local _M = {
+ version = 0.1,
+ priority = 21991,
+ name = plugin_name,
+ schema = schema,
+}
+
+
+function _M.check_schema(conf)
+ return core.schema.check(schema, conf)
+end
+
+
+-- we want to control proxy behavior before auth, so put the code under
rewrite method
+function _M.rewrite(conf, ctx)
+ ngx.ctx.disable_proxy_buffering = conf.disable_proxy_buffering
+end
+
+
+return _M
diff --git a/ci/linux_apisix_current_luarocks_runner.sh
b/ci/linux_apisix_current_luarocks_runner.sh
index 0d2e4e9b8..742e16926 100755
--- a/ci/linux_apisix_current_luarocks_runner.sh
+++ b/ci/linux_apisix_current_luarocks_runner.sh
@@ -73,7 +73,7 @@ script() {
set_coredns
# install test dependencies
- sudo pip install requests
+ sudo pip install requests aiohttp aiohttp-sse
# dismiss "maximum number of open file descriptors too small" warning
ulimit -n 10240
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index 495ad198d..58052cb3d 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -476,6 +476,7 @@ plugins: # plugin list (sorted by
priority)
- ai # priority: 22900
#- exit-transformer # priority: 22950, disabled by default
- client-control # priority: 22000
+ - proxy-buffering # priority: 21991
- proxy-control # priority: 21990
- request-id # priority: 12015
- zipkin # priority: 12011
diff --git a/docs/en/latest/config.json b/docs/en/latest/config.json
index ca3f544f4..1dc91c6b3 100644
--- a/docs/en/latest/config.json
+++ b/docs/en/latest/config.json
@@ -178,6 +178,7 @@
"plugins/traffic-label",
"plugins/request-id",
"plugins/proxy-control",
+ "plugins/proxy-buffering",
"plugins/client-control",
"plugins/workflow"
]
diff --git a/docs/en/latest/plugins/proxy-buffering.md
b/docs/en/latest/plugins/proxy-buffering.md
new file mode 100644
index 000000000..d8f4f864c
--- /dev/null
+++ b/docs/en/latest/plugins/proxy-buffering.md
@@ -0,0 +1,109 @@
+---
+title: proxy-buffering
+keywords:
+ - Apache APISIX
+ - API Gateway
+ - Proxy Buffering
+description: The proxy-buffering Plugin disables nginx proxy buffering per
route to enable streaming responses such as Server-Sent Events (SSE).
+---
+
+<!--
+#
+# 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/proxy-buffering" />
+</head>
+
+## Description
+
+The `proxy-buffering` Plugin disables nginx proxy buffering for the configured
route. When proxy buffering is disabled, nginx streams the upstream response
directly to the client without accumulating it in memory or on disk first.
+
+This is particularly useful for:
+
+- **Server-Sent Events (SSE)**: Clients must receive events in real time;
buffering would delay or break the stream.
+- **Streaming APIs**: Large or indefinite response bodies must flow
continuously without waiting for the full body.
+- **Real-time data delivery**: Any use case requiring low-latency delivery of
partial responses.
+
+## Attributes
+
+| Name | Type | Required | Default | Description
|
+| ------------------------- | ------- | -------- | ------- |
---------------------------------------------------------------------------------------------
|
+| disable_proxy_buffering | boolean | No | false | When set to
`true`, disables
[`proxy_buffering`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering)
for this route, enabling streaming responses. |
+
+## Examples
+
+The examples below demonstrate how you can configure the `proxy-buffering`
Plugin for different scenarios.
+
+:::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')
+```
+
+:::
+
+### Disable Proxy Buffering for Streaming Responses
+
+The following example disables proxy buffering for a route that serves
Server-Sent Events (SSE):
+
+```shell
+curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
+ -H "X-API-KEY: $admin_key" -X PUT -d '
+{
+ "uri": "/sse",
+ "plugins": {
+ "proxy-buffering": {
+ "disable_proxy_buffering": true
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+}'
+```
+
+Send a request to the route:
+
+```shell
+curl -i -N -H "Accept: text/event-stream" http://127.0.0.1:9080/sse
+```
+
+Because `disable_proxy_buffering` is `true`, nginx streams each SSE event from
the upstream to the client as it arrives, without buffering.
+
+## Delete Plugin
+
+To remove the `proxy-buffering` Plugin, 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.
+
+```shell
+curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
+ -H "X-API-KEY: $admin_key" -X PUT -d '
+{
+ "uri": "/sse",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+}'
+```
diff --git a/docs/zh/latest/config.json b/docs/zh/latest/config.json
index ace1bcac7..6240fe6e4 100644
--- a/docs/zh/latest/config.json
+++ b/docs/zh/latest/config.json
@@ -166,6 +166,7 @@
"plugins/traffic-label",
"plugins/request-id",
"plugins/proxy-control",
+ "plugins/proxy-buffering",
"plugins/client-control",
"plugins/workflow"
]
diff --git a/docs/zh/latest/plugins/proxy-buffering.md
b/docs/zh/latest/plugins/proxy-buffering.md
new file mode 100644
index 000000000..a733df859
--- /dev/null
+++ b/docs/zh/latest/plugins/proxy-buffering.md
@@ -0,0 +1,117 @@
+---
+title: proxy-buffering
+keywords:
+ - APISIX
+ - API 网关
+ - Proxy Buffering
+description: 本文介绍了 Apache APISIX proxy-buffering 插件的相关操作,你可以使用此插件按路由禁用 nginx
代理缓冲,这对于流式响应(如 Server-Sent Events)至关重要。
+---
+
+<!--
+#
+# 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/proxy-buffering" />
+</head>
+
+## 描述
+
+`proxy-buffering` 插件用于按路由控制 nginx 代理缓冲行为。禁用代理缓冲后,nginx
会将上游响应直接流式传输给客户端,而不会在内存或磁盘中积累完整响应体。该功能对以下场景至关重要:
+
+- **Server-Sent Events(SSE)**:客户端需要实时接收事件,缓冲会延迟或中断数据流。
+- **流式 API**:响应体较大或无限长,需要持续流式传输而无需等待完整响应。
+- **实时数据推送**:任何需要低延迟传输部分响应的场景。
+
+该插件工作在 `rewrite` 阶段,优先级为 **21991**,早于鉴权插件执行,可以影响 APISIX 流水线中代理 location 的选择。
+
+## 属性
+
+| 名称 | 类型 | 必选项 | 默认值 | 描述
|
+| ----------------------- | ------- | ------ | ------ |
-------------------------------------------------------------------------------------------------------------------------------------------------
|
+| disable_proxy_buffering | boolean | 否 | false | 设置为 `true` 时,将为该路由禁用
[`proxy_buffering`](https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering),从而支持流式响应。
|
+
+## 启用插件
+
+以下示例展示了如何在指定路由上启用 `proxy-buffering` 插件以支持流式响应:
+
+:::note
+
+您可以这样从 `config.yaml` 中获取 `admin_key` 并存入环境变量:
+
+```bash
+admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed
's/"//g')
+```
+
+:::
+
+```shell
+curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
+ -H "X-API-KEY: $admin_key" -X PUT -d '
+{
+ "uri": "/sse",
+ "plugins": {
+ "proxy-buffering": {
+ "disable_proxy_buffering": true
+ }
+ },
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+}'
+```
+
+## 测试插件
+
+启用插件后,向该路由发送请求:
+
+```shell
+curl -i http://127.0.0.1:9080/sse
+```
+
+由于 `disable_proxy_buffering` 设置为 `true`,nginx 会将响应直接流式传输给客户端,对调用方透明,但消除了 nginx
引入的缓冲延迟。
+
+要验证配置是否已正确保存:
+
+```shell
+curl http://127.0.0.1:9180/apisix/admin/routes/1 \
+ -H "X-API-KEY: $admin_key"
+```
+
+响应中将包含路由对象中的 `proxy-buffering` 插件配置。
+
+## 删除插件
+
+当你需要删除该插件时,可以通过以下命令删除相应的 JSON 配置,APISIX 将会自动重新加载相关配置,无需重启服务:
+
+```shell
+curl -i http://127.0.0.1:9180/apisix/admin/routes/1 \
+ -H "X-API-KEY: $admin_key" -X PUT -d '
+{
+ "uri": "/sse",
+ "upstream": {
+ "type": "roundrobin",
+ "nodes": {
+ "127.0.0.1:1980": 1
+ }
+ }
+}'
+```
diff --git a/t/APISIX.pm b/t/APISIX.pm
index 9f862cfec..945305068 100644
--- a/t/APISIX.pm
+++ b/t/APISIX.pm
@@ -224,6 +224,43 @@ $grpc_location .= <<_EOC_;
}
_EOC_
+my $disable_proxy_buffering_location = <<_EOC_;
+ location \@disable_proxy_buffering {
+ access_by_lua_block {
+ apisix.disable_proxy_buffering_access_phase()
+ }
+
+ proxy_http_version 1.1;
+ proxy_set_header Host \$upstream_host;
+ proxy_set_header Upgrade \$upstream_upgrade;
+ proxy_set_header Connection \$upstream_connection;
+ proxy_set_header X-Real-IP \$remote_addr;
+ proxy_pass_header Date;
+
+ proxy_set_header X-Forwarded-For
\$proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto \$var_x_forwarded_proto;
+ proxy_set_header X-Forwarded-Host \$var_x_forwarded_host;
+ proxy_set_header X-Forwarded-Port \$var_x_forwarded_port;
+
+ proxy_pass
\$upstream_scheme://apisix_backend\$upstream_uri;
+ mirror /proxy_mirror;
+
+ header_filter_by_lua_block {
+ apisix.http_header_filter_phase()
+ }
+
+ body_filter_by_lua_block {
+ apisix.http_body_filter_phase()
+ }
+
+ log_by_lua_block {
+ apisix.http_log_phase()
+ }
+
+ proxy_buffering off;
+ }
+_EOC_
+
my $a6_ngx_directives = "";
if ($version =~ m/\/apisix-nginx-module/) {
$a6_ngx_directives = <<_EOC_;
@@ -934,6 +971,7 @@ _EOC_
$grpc_location
$dubbo_location
+ $disable_proxy_buffering_location
location = /proxy_mirror {
internal;
diff --git a/t/admin/plugins.t b/t/admin/plugins.t
index 1887ed5ff..6061de721 100644
--- a/t/admin/plugins.t
+++ b/t/admin/plugins.t
@@ -60,6 +60,7 @@ __DATA__
real-ip
ai
client-control
+proxy-buffering
proxy-control
request-id
zipkin
diff --git a/t/certs/server.crt b/t/certs/server.crt
new file mode 100644
index 000000000..a274211e7
--- /dev/null
+++ b/t/certs/server.crt
@@ -0,0 +1,18 @@
+-----BEGIN CERTIFICATE-----
+MIIC/DCCAeSgAwIBAgIUb/83dlLgiY9Rmxnx15Rzoy/wSbswDQYJKoZIhvcNAQEL
+BQAwGDEWMBQGA1UEAwwNY2EuYXBpc2l4LmRldjAeFw0yMzA2MTYwNjEzNTVaFw0z
+MzA2MTMwNjEzNTVaMBQxEjAQBgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAK7wA2DpYvpprOT9ArQ/OGVOzK3djelD3QDrxzCU
+9mWzxA0NVVxdSyW+Fa0Mw1Vqi3n/eoPUrFY2Cu1P3wcZrCmGJx69rLRS4a1+SRAt
+8BmJCB0yAYNitCalhzvcuhlJ1jlGcPFqFJpq3pKF3xVkhpOozsW1f0YvChlTalc9
+0ajpSLIDbJfNEuAUKm+vxtlNx54esZKqyYF2nOj9YnyQ2VR7IlXuTYvtAbgOQ9nZ
+WHnxKKKFk5YcWluVi9ZqSLugQDx9pxt+H+kldVnWctSAg+IDLh8Drri15Nri7dQB
+mLOszeGsonM/3xohkfh9KsNc9+ud6T6+mwUXN/+cQZUhoo0CAwEAAaNCMEAwHQYD
+VR0OBBYEFM1oa6flhpyc10Xuqt80/hiN01b6MB8GA1UdIwQYMBaAFAMzLkPCZDgG
+4p0zc5DZ88T3ZOU+MA0GCSqGSIb3DQEBCwUAA4IBAQBXFpEqrbKVjadDxVUm68/Y
+fzzLODHOsgV4QioWjdVLdmWw1zz7Vipw7iClOWP4zt5F/8WmnDdFK/U+g+03vvqg
+No6NDodOuYwZZFKRISznobAd/gH4tpg9X7ggY3ZB68EFBeGYivr6qnKhze9vZTT4
+DP8eyiMNL/zOvpTkwPCXZQKSj1TvJRhZpB1YsAb+JW775ME1QIN4HSoMnIsX1JQx
++5tmcz7GBKVlggpFsg4fwRieh+hnOD4gyrWtIAsSmLDqtUercWKMBwCqRZ7COlMM
+UGkL2W3Cl44hNJJqVGztqw52u4kOxRm38E8uBVsXdWTuca+Z/xhr584z3O0CNxiw
+-----END CERTIFICATE-----
diff --git a/t/certs/server.key b/t/certs/server.key
new file mode 100644
index 000000000..a7eda9ebb
--- /dev/null
+++ b/t/certs/server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCu8ANg6WL6aazk
+/QK0PzhlTsyt3Y3pQ90A68cwlPZls8QNDVVcXUslvhWtDMNVaot5/3qD1KxWNgrt
+T98HGawphicevay0UuGtfkkQLfAZiQgdMgGDYrQmpYc73LoZSdY5RnDxahSaat6S
+hd8VZIaTqM7FtX9GLwoZU2pXPdGo6UiyA2yXzRLgFCpvr8bZTceeHrGSqsmBdpzo
+/WJ8kNlUeyJV7k2L7QG4DkPZ2Vh58SiihZOWHFpblYvWaki7oEA8facbfh/pJXVZ
+1nLUgIPiAy4fA664teTa4u3UAZizrM3hrKJzP98aIZH4fSrDXPfrnek+vpsFFzf/
+nEGVIaKNAgMBAAECggEAA/b2x9aYqPhsWmcGwMM7PRLx0TG99B69UdAfwqpozsap
+6rcCvr+mItpYHATRVOYmK0mOr/f9DRnxDUGfDXmkk1WLg6C+QWEssf80Z7ReBmFU
+Xww3+/YCNp5pkYIMo6/iAruydpfPx+1XhjZlGmzqhReX0n152RAODtaubwC8xJlP
+SMN4MtucQOtQaYB9MKKJXVGjHDlcEZ8zpGfBnwfm94eq1PU/no2O2+FxJvcbwo1i
+Z1LvCRMEYRjw8dLN/9J87IaYpyxQKdMpXJndWZM5gsPQYj2fu0BmG7DRMO5JwrPH
++YSpwVk8Zj6iqkgrgDZd8C/ip5Ad+I4JvBmk2bBY/wKBgQDESC8nKt53UR5wo0cS
+qcuLtvo4qBLek7NFvuUoHB/va5FTCdk6DEqwwT9dxK0tbkVpwOQP35qKE/9Z1fGs
++Wiqk37jCqQw3q80W0iy9w8LBvwY707SSIW2hlqHyKX6z1Sgkxb7LEuYMFabcHYJ
+/njbXDUFmHaxx/DpwRIQd35j9wKBgQDkKWBIQ8E1CUB4l/GXFnpkU/e03k9FXfRY
+8ECW1J8+SiaJilubqMvnVnuTik/x4cfhtokZQ75aWfBmKaxCcHQo4lE1EUm8EZfZ
+LHb49ani9acA1sUD/4OXQx7Z4FvTOHJlcX9SGNDrJ4ZP4KoTe8iGosdIBW0ZgNl0
+OZRi+P7EmwKBgQCqhCMinaVidoBBTjdiSUvg9nbuXWT3I80sHCir7bL4pbQ8NWQX
+f7IrV9aauU/RFekLdF0sRQ7WQYUmICyyHoIFHdmvmAEHy4zdqSfBAcx5wfmkF9Gb
+8RDoflOLfo5jE1broJzoW5TYg7XZl5uUFxQbTUFquQb0b0QlbmspDwYoBQKBgBcu
+yRSupbwA06ctMSP76bW1/m5HRGC0+jXrO3TX59JJcH77KvG3BfjchbPpEtRt74RU
+qruBcZ72koGe7FzX1kWtMTkmJ4rPctFjPjdvprJj6XMhX2VXmgRNkp3quQnOK3l5
+PcmWseui2XfWSRGvuyFK+tqrinlT6URw8U3ZJPHPAoGAM6KCsgr8utVYNUcgwS17
+6jn/Im8OxGzZFdwVKbBzSbX/BIr2yCqminG3huhkKj3Xs0+3/BRsOIriEtYPNmnQ
+04G+D1RCWmOPQG0SKaRmXTGvTnbMbpuSEn5f6+MoTyETgINSgzPDCyoiYdEVqRen
+w8CY8chA3UySYrj9hu50tJ0=
+-----END PRIVATE KEY-----
diff --git a/t/certs/sse_server.crt b/t/certs/sse_server.crt
new file mode 100644
index 000000000..471c0af56
--- /dev/null
+++ b/t/certs/sse_server.crt
@@ -0,0 +1,19 @@
+-----BEGIN CERTIFICATE-----
+MIIC/TCCAeWgAwIBAgIUb/83dlLgiY9Rmxnx15Rzoy/wSb0wDQYJKoZIhvcNAQEL
+BQAwGDEWMBQGA1UEAwwNY2EuYXBpc2l4LmRldjAeFw0yMzA2MTYwNjE5MjNaFw0z
+MzA2MTMwNjE5MjNaMBUxEzARBgNVBAMMCnNzZS5zZXJ2ZXIwggEiMA0GCSqGSIb3
+DQEBAQUAA4IBDwAwggEKAoIBAQDFGBdWQztEX6GWYNi0zhPyNG9RIsrLGJI3iUcA
+iyQrZnkSvORVEKzt6vwoEbhChCQbZcyLWW95a/rzK+4TXfZvX520iBf3hmcHK8+e
+m0WW9BUttKOkjlAGZxi6RUIkUV6HE76C0vm5fX+eawBZSDlwg+rd8DcfyGcrrMwv
+AwiftmFMpMN5QZEtmchwGFVmN8T8JWmiC3w+50njO4mfovjiXFaxBZ+X2T5pZLFh
+FXHPqRMtaX6khFqK/s1BwMuZ2BkKQOVBEkQZMN5w5HtyxhanrNId8EB8ss5ijG0+
+a0BpSGQ0FLQlWREqPUpwNdO2nPEHzxLJMr5bEwYiuqki/k1HAgMBAAGjQjBAMB0G
+A1UdDgQWBBTF3aCnfK9c1t1oFBxsS6AxQUjTtjAfBgNVHSMEGDAWgBQDMy5DwmQ4
+BuKdM3OQ2fPE92TlPjANBgkqhkiG9w0BAQsFAAOCAQEAPqFY+p5NRLJitneDgjCj
+6FBTvkFpfkUFeUWXtnbrIROXYu6TMf7TujlXEGEPsuuetFAfpPZqAeSv3za3lFuP
+bF9T2uYOU6B/pxMTofvB9XgOe80pNNlsZ7Qer3ikNDn646r5Xn0Q9u6ESsaKREOW
++CcT4uzt9/sC6Fpi2DKM6qu/w0juszDrwoBaXQHk4Q5tzQRr+jLrcqnFJmhEQZ+y
+Wyat2pUccB5T2/y4+TxH6V6XSi0CkADchl4i5bmfjK6I9WycQJRC+qMOiuKBcJzD
+UDuBPnk9GPhgjpZ3iGhMe4GpFcGcHfCUbnw7BdRPc09RV6S5j22qb0u9EahC1IJD
+wA==
+-----END CERTIFICATE-----
diff --git a/t/certs/sse_server.key b/t/certs/sse_server.key
new file mode 100644
index 000000000..fdd7016a6
--- /dev/null
+++ b/t/certs/sse_server.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDFGBdWQztEX6GW
+YNi0zhPyNG9RIsrLGJI3iUcAiyQrZnkSvORVEKzt6vwoEbhChCQbZcyLWW95a/rz
+K+4TXfZvX520iBf3hmcHK8+em0WW9BUttKOkjlAGZxi6RUIkUV6HE76C0vm5fX+e
+awBZSDlwg+rd8DcfyGcrrMwvAwiftmFMpMN5QZEtmchwGFVmN8T8JWmiC3w+50nj
+O4mfovjiXFaxBZ+X2T5pZLFhFXHPqRMtaX6khFqK/s1BwMuZ2BkKQOVBEkQZMN5w
+5HtyxhanrNId8EB8ss5ijG0+a0BpSGQ0FLQlWREqPUpwNdO2nPEHzxLJMr5bEwYi
+uqki/k1HAgMBAAECggEALSIUqgDQUBp0FssLpO+x+ptOSG6msLZqOUR62WGDgVrA
+a+2MffxJFVxjrMtN/hFjcVCw89IhqFT1TP0o0g+I0L09EGu/zUNeUXKTYzccSvKO
+7P36IUMjiSvPqkwU1ts5QcZgMHYekH7wG/dVx5w15xGWVYdeIC2UjphN05Amx+el
+XkWJsRg5zQadfgGpX875J86sdY/KBWYeA2lxpxZ33EbEfHbuJvLFPqJ69FN8mOAf
+6SFcyjShP8ovpuhX/DT/g3hi7dQtRR6YoKb7LBiNwFkpdNkEA8rWMkcA1F0NHlf+
+KEs9O9iD+im/v62KYJtZTCQoPNuFVArPVUH0i0HMKQKBgQDlw+ygKV5pdUDTHuhA
+lVlEokh3oa7MFGywh1T5OUVvUqCSJ5NPX5L+3cAENS4uLje/3hM5gCIyVAfN0ri7
+OiXpeR+D2fq1js5ccKXvJNQy6knxDnB2BLAlPipSp4Iih6PMVF0hKvyzBl2p3nTT
+S7fqFaDc6sIOBsWWRJcghS7XrwKBgQDbmS6XG+VBTZLRtMmkBPv+2XGcaH9s9lI/
+4UmYDvUZi4bFqtSrKKi0WkpNCP6x4TXzH41v/bJqmQqs40Y1ZctraEt/rV6nR0E5
+IgSAkTFXB2T7Cjhcvt9Xj0QfU01+4mIRBkDQnbjrcCu1QOtDISGi5nQtK0QAd6UD
+/aykToSx6QKBgAwFjUrv/y2bYfHp6xL9/Xa22v3Patrosqsl2Y9UrMpfU2FySqXb
+hVBqf9J4idsGtgoG75CRoLhrZyEgxmOdbkBiAwEeFZ0MRMXXawcxMR0c3xOKwt2Z
+7zFzqDk85HU0DaDyRREoM6KWUa5ConAvxQatbQZCDjc3qXzsR8/+x+2nAoGBAMA0
+uVTFs8mOrl0ikgMf4bjUdd5ikHW8u4zyEUoofVsYhqPovC/7bH4/MR1wLA1hg6kD
+Cvbk5Q7sWS2t17vRF1UxejOMeXaMpYfuQGaPrtHvxPD9pwt2fWHUIdoRPZk7aH5i
+LMTr5/kauwbwhXrCOwCsGS+X2PNXxXVSyZMeroJRAoGBAL5d2VhMevFerYzuPA8f
+yNMT4H5IOW9XeB/fxJGqOdVyn3aY3TBoNXFSQKLWnK/0DuKB7nwB/klQ9hL8OW9u
+QfDXwtXRk0XznUDcxjcx723KdRRipEZd4QaZNEPfhaadmB1jZN8rNDSXJFqhmN4g
+ydkZABH8wRc0Bc1qI/Gr/bRe
+-----END PRIVATE KEY-----
diff --git a/t/cli/test_proxy_buffering.sh b/t/cli/test_proxy_buffering.sh
new file mode 100755
index 000000000..e37519602
--- /dev/null
+++ b/t/cli/test_proxy_buffering.sh
@@ -0,0 +1,94 @@
+#!/usr/bin/env bash
+#
+# 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.
+#
+
+. ./t/cli/common.sh
+
+# Admin API curl wrapper
+c() {
+ method=${1^^}
+ resource=$2
+ shift 2
+ local admin_key
+ admin_key=$(yq '.deployment.admin.admin_key[0].key' conf/config.yaml | sed
's/"//g')
+ curl --fail-with-body
${ADMIN_SCHEME:-http}://${ADMIN_IP:-127.0.0.1}:${ADMIN_PORT:-9180}/apisix/admin${resource}
\
+ -H "X-API-KEY: ${admin_key}" -X "$method" "$@"
+}
+
+make run
+
+c put /routes/1 -d '{
+ "uri": "/*",
+ "upstream": {
+ "scheme": "http",
+ "type": "roundrobin",
+ "nodes": {
+ "localhost:8080": 1
+ }
+ }
+}'
+
+timeout 10 python3 -u t/cli/test_sse.py
+
+c put /routes/1 -d '{
+ "uri": "/*",
+ "plugins": {
+ "proxy-buffering": {
+ "disable_proxy_buffering": true
+ }
+ },
+ "upstream": {
+ "scheme": "http",
+ "type": "roundrobin",
+ "nodes": {
+ "localhost:8080": 1
+ }
+ }
+}'
+
+timeout 10 python3 -u t/cli/test_sse.py
+
+c put /ssls/1 -d '{
+ "cert": "'"$(<t/certs/server.crt)"'",
+ "key": "'"$(<t/certs/server.key)"'",
+ "snis": [
+ "localhost"
+ ]
+}'
+
+c put /routes/1 -d '{
+ "uri": "/*",
+ "plugins": {
+ "proxy-buffering": {
+ "disable_proxy_buffering": true
+ }
+ },
+ "upstream": {
+ "scheme": "https",
+ "type": "roundrobin",
+ "nodes": {
+ "localhost:8080": 1
+ }
+ }
+}'
+
+timeout 10 python3 -u t/cli/test_sse.py ssl
+
+c delete /routes/1
+c delete /ssls/1
+
+make stop
diff --git a/t/cli/test_sse.py b/t/cli/test_sse.py
new file mode 100644
index 000000000..a61268dd9
--- /dev/null
+++ b/t/cli/test_sse.py
@@ -0,0 +1,84 @@
+# 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.
+#
+from threading import Thread
+import asyncio
+from aiohttp import web
+from aiohttp_sse import sse_response
+from aiohttp_sse import EventSourceResponse
+from datetime import datetime
+import time
+import os
+import sys
+import ssl
+
+
+test_ssl = len(sys.argv) == 2 and sys.argv[1] == "ssl"
+
+
+class SseResponse(EventSourceResponse):
+ def __init__(self, **args):
+ super().__init__(**args)
+ self.headers.pop("X-Accel-Buffering", None)
+
+
+async def events(request):
+ async with sse_response(request, response_cls=SseResponse) as resp:
+ for i in range(30000):
+ data = "Server Time : {}".format(datetime.now())
+ print(data)
+ await resp.send(data)
+ await asyncio.sleep(1)
+
+
+def run_server():
+ if test_ssl:
+ ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
+ ssl_context.load_cert_chain("t/certs/sse_server.crt",
"t/certs/sse_server.key")
+ app = web.Application()
+ app.router.add_route("GET", "/events", events)
+ if test_ssl:
+ web.run_app(app, host="127.0.0.1", port=8080, ssl_context=ssl_context)
+ else:
+ web.run_app(app, host="127.0.0.1", port=8080)
+
+
+def run_client():
+ time.sleep(2)
+ print("start testing sse...")
+
+ import requests
+
+ if test_ssl:
+ url = "https://localhost:9443/events"
+ else:
+ url = "http://localhost:9080/events"
+ headers = {"Accept": "text/event-stream"}
+ response = requests.get(url, stream=True, headers=headers, verify=False)
+ i = 0
+ for line in response.iter_lines():
+ if line:
+ decoded = line.decode("utf-8")
+ if decoded.startswith("data:"):
+ print(decoded)
+ i += 1
+ if i == 3:
+ print("sse proxy test ok")
+ os._exit(0)
+
+
+t = Thread(target=run_client, daemon=True)
+t.start()
+run_server()