Compare commits

...

27 Commits

Author SHA1 Message Date
澄潭
37fb2a52c0 rel: Release 1.3.6 (#918)
Signed-off-by: johnlanni <zty98751@alibaba-inc.com>
2024-04-22 19:33:42 +08:00
澄潭
41491166e3 Update Makefile.core.mk 2024-04-22 19:26:36 +08:00
澄潭
29baf8576e Fix the issue of istio VirtualService not supporting multiple domain names when using SRDS (#917) 2024-04-22 19:03:53 +08:00
fsl
f1cadcbd73 fix: get the container access docker compose ps (#870)
Signed-off-by: fengshunli <1171313930@qq.com>
2024-04-18 18:56:16 +08:00
澄潭
8c817cf80a Update README.md 2024-04-16 09:45:59 +08:00
澄潭
a67ce1d223 Update httproute-limit.go 2024-04-10 11:02:14 +08:00
澄潭
fb18782a80 Update build-and-test.yaml 2024-04-10 10:32:44 +08:00
澄潭
026840b59b Update README_EN.md 2024-04-10 10:31:08 +08:00
澄潭
75599ef804 Update README.md 2024-04-10 10:30:27 +08:00
Kent Dong
fe039d46f2 fix: Fix the "Build Status" badge on README pages (#904) 2024-04-10 10:22:28 +08:00
alexzzh
6c7b1757b6 feat: higress global configmap support config route timeout (#883) 2024-04-07 10:45:25 +08:00
Kent Dong
dfc9ae412e fix: Improve error handling in the plugin config model parser of hgctl (#891) 2024-04-03 09:49:06 +08:00
alexzzh
b4f72d3584 optimize on construct envoyfilter (#889) 2024-04-01 19:18:38 +08:00
澄潭
cba2890e14 Update README.md 2024-03-29 16:49:01 +08:00
澄潭
e844daea66 Feat: transformer plugin support map from body to header (#892) 2024-03-29 16:20:16 +08:00
澄潭
717e3bf51f optimize logic for empty config wasm plugin (#898) 2024-03-29 16:17:36 +08:00
澄潭
ba0df237da fix wasm priority logic (#897) 2024-03-29 14:03:02 +08:00
dongdongh233
08e56780f0 e2e: add testcases for rate limit annotations (#879) 2024-03-27 14:57:32 +08:00
澄潭
a45748bb0b fix priority type of wasmplugin (#881) 2024-03-26 19:54:08 +08:00
brother-戎
97cf58e973 test: add httproute-response-header-control for #863 (#875) 2024-03-26 19:08:23 +08:00
Kent Dong
4d6aa25b19 fix: Consider the new ingress package is available as default (#880) 2024-03-26 16:12:56 +08:00
rinfx
25c2f6e42e update redis wrapper (#864) 2024-03-18 16:37:10 +08:00
Kent Dong
ed55b65443 feat: Support publishing hgctl packages to GitHub releases (#869) 2024-03-13 13:41:36 +08:00
澄潭
d64c266ee4 fix file name (#867) 2024-03-12 16:45:05 +08:00
Bowen Li
32b602704e feat: add plugin - cache control (#810) 2024-03-12 16:42:53 +08:00
renz7
3128df9abd feat: add ip-restriction wasm-go plugin (#759) 2024-03-12 16:25:44 +08:00
澄潭
cc6043de15 fix route name from gateway api (#866) 2024-03-08 17:35:04 +08:00
52 changed files with 2383 additions and 352 deletions

View File

@@ -48,7 +48,7 @@ jobs:
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
fail_ci_if_error: true
fail_ci_if_error: false
files: ./coverage.xml
verbose: true

37
.github/workflows/release-hgctl.yaml vendored Normal file
View File

@@ -0,0 +1,37 @@
name: Release hgctl to GitHub
on:
push:
tags:
- "v*.*.*"
workflow_dispatch: ~
jobs:
release-hgctl:
runs-on: ubuntu-latest
env:
HGCTL_VERSION: ${{github.ref_name}}
steps:
- uses: actions/checkout@v3
- name: Build hgctl latest multiarch binaries
run: |
GOPROXY="https://proxy.golang.org,direct" make build-hgctl-multiarch
tar -zcvf hgctl_${{ env.HGCTL_VERSION }}_linux_amd64.tar.gz out/linux_amd64/
tar -zcvf hgctl_${{ env.HGCTL_VERSION }}_linux_arm64.tar.gz out/linux_arm64/
tar -zcvf hgctl_${{ env.HGCTL_VERSION }}_darwin_amd64.tar.gz out/darwin_amd64/
tar -zcvf hgctl_${{ env.HGCTL_VERSION }}_darwin_arm64.tar.gz out/darwin_arm64/
zip -q -r hgctl_${{ env.HGCTL_VERSION }}_windows_amd64.zip out/windows_amd64/
zip -q -r hgctl_${{ env.HGCTL_VERSION }}_windows_arm64.zip out/windows_arm64/
- name: Upload hgctl packages to the GitHub release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
with:
files: |
hgctl_${{ env.HGCTL_VERSION }}_linux_amd64.tar.gz
hgctl_${{ env.HGCTL_VERSION }}_linux_arm64.tar.gz
hgctl_${{ env.HGCTL_VERSION }}_darwin_amd64.tar.gz
hgctl_${{ env.HGCTL_VERSION }}_darwin_arm64.tar.gz
hgctl_${{ env.HGCTL_VERSION }}_windows_amd64.zip
hgctl_${{ env.HGCTL_VERSION }}_windows_arm64.zip

View File

@@ -177,8 +177,8 @@ install: pre-install
cd helm/higress; helm dependency build
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
ENVOY_LATEST_IMAGE_TAG ?= sha-eb1f993
ISTIO_LATEST_IMAGE_TAG ?= sha-eb1f993
ENVOY_LATEST_IMAGE_TAG ?= sha-29baf85
ISTIO_LATEST_IMAGE_TAG ?= sha-29baf85
install-dev: pre-install
helm install higress helm/core -n higress-system --create-namespace --set 'controller.tag=$(TAG)' --set 'gateway.replicas=1' --set 'pilot.tag=$(ISTIO_LATEST_IMAGE_TAG)' --set 'gateway.tag=$(ENVOY_LATEST_IMAGE_TAG)' --set 'global.local=true'

View File

@@ -1,10 +1,10 @@
<h1 align="center">
<img src="https://img.alicdn.com/imgextra/i2/O1CN01NwxLDd20nxfGBjxmZ_!!6000000006895-2-tps-960-290.png" alt="Higress" width="240" height="72.5">
<br>
Next-generation Cloud Native Gateway
Cloud Native API Gateway
</h1>
[![Build Status](https://github.com/alibaba/higress/workflows/build%20and%20codecov/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[**官网**](https://higress.io/) &nbsp; |
@@ -19,7 +19,7 @@
</p>
Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源 [Istio](https://github.com/istio/istio) 与 [Envoy](https://github.com/envoyproxy/envoy) 为核心构建的下一代云原生网关。Higress 实现了安全防护网关、流量网关、微服务网关三层网关合一,可以显著降低网关的部署和运维成本。
Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源 [Istio](https://github.com/istio/istio) 与 [Envoy](https://github.com/envoyproxy/envoy) 为核心构建的云原生 API 网关。Higress 实现了安全防护网关、流量网关、微服务网关三层网关合一,可以显著降低网关的部署和运维成本。
![arch](https://img.alicdn.com/imgextra/i1/O1CN01iO9ph825juHbOIg75_!!6000000007563-2-tps-2483-2024.png)
@@ -119,9 +119,13 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
如果没有 Envoy 和 Istio 的开源工作Higress 就不可能实现,在这里向这两个项目献上最诚挚的敬意。
### 联系我们
社区交流群:
### 交流群
![image](https://img.alicdn.com/imgextra/i2/O1CN01qPd7Ix1uZPVEsWjWp_!!6000000006051-0-tps-720-405.jpg)
### 技术分享
微信公众号:
![](https://img.alicdn.com/imgextra/i1/O1CN01WnQt0q1tcmqVDU73u_!!6000000005923-0-tps-258-258.jpg)

View File

@@ -1,10 +1,10 @@
<h1 align="center">
<img src="https://img.alicdn.com/imgextra/i2/O1CN01NwxLDd20nxfGBjxmZ_!!6000000006895-2-tps-960-290.png" alt="Higress" width="240" height="72.5">
<br>
Next-generation Cloud Native Gateway
Cloud Native API Gateway
</h1>
[![Build Status](https://github.com/alibaba/higress/workflows/build%20and%20codecov/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions)
[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html)
[**Official Site**](https://higress.io/en-us/) &nbsp; |
@@ -18,7 +18,7 @@
English | <a href="README.md">中文<a/>
</p>
Higress is a next-generation cloud-native gateway based on Alibaba's internal gateway practices.
Higress is a cloud-native api gateway based on Alibaba's internal gateway practices.
Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.com/envoyproxy/envoy), Higress realizes the integration of the triple gateway architecture of traffic gateway, microservice gateway and security gateway, thereby greatly reducing the costs of deployment, operation and maintenance.

View File

@@ -1 +1 @@
v1.3.5
v1.3.6

View File

@@ -166,7 +166,7 @@ type WasmPlugin struct {
// If `priority` is not set, or two `WasmPlugins` exist with the same
// value, the ordering will be deterministically derived from name and
// namespace of the `WasmPlugins`. Defaults to `0`.
Priority *types.Int64Value `protobuf:"bytes,10,opt,name=priority,proto3" json:"priority,omitempty"`
Priority *types.Int32Value `protobuf:"bytes,10,opt,name=priority,proto3" json:"priority,omitempty"`
// Extended by Higress, the default configuration takes effect globally
DefaultConfig *types.Struct `protobuf:"bytes,101,opt,name=default_config,json=defaultConfig,proto3" json:"default_config,omitempty"`
// Extended by Higress, matching rules take effect
@@ -267,7 +267,7 @@ func (m *WasmPlugin) GetPhase() PluginPhase {
return PluginPhase_UNSPECIFIED_PHASE
}
func (m *WasmPlugin) GetPriority() *types.Int64Value {
func (m *WasmPlugin) GetPriority() *types.Int32Value {
if m != nil {
return m.Priority
}
@@ -377,46 +377,46 @@ func init() {
func init() { proto.RegisterFile("extensions/v1alpha1/wasm.proto", fileDescriptor_4d60b240916c4e18) }
var fileDescriptor_4d60b240916c4e18 = []byte{
// 617 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x94, 0xdf, 0x4e, 0x13, 0x41,
0x14, 0xc6, 0xd9, 0x16, 0x0a, 0x3d, 0x05, 0x5c, 0x26, 0x8a, 0x13, 0x30, 0xb5, 0x21, 0x51, 0x57,
0x2e, 0x76, 0x43, 0x45, 0xbc, 0x31, 0xc4, 0x02, 0x55, 0x1a, 0xb5, 0x6e, 0x76, 0x41, 0x23, 0x37,
0x9b, 0xe9, 0x32, 0xdd, 0x4e, 0x9c, 0xfd, 0x93, 0x9d, 0x59, 0xb0, 0x0f, 0xe2, 0x3b, 0x79, 0xe9,
0x23, 0x18, 0xde, 0xc2, 0x3b, 0xd3, 0xd9, 0x2d, 0x6d, 0xd1, 0xf4, 0x6e, 0xe6, 0x9c, 0xdf, 0x39,
0xe7, 0xfb, 0xce, 0x4e, 0x16, 0xea, 0xf4, 0xbb, 0xa4, 0x91, 0x60, 0x71, 0x24, 0xac, 0xab, 0x3d,
0xc2, 0x93, 0x01, 0xd9, 0xb3, 0xae, 0x89, 0x08, 0xcd, 0x24, 0x8d, 0x65, 0x8c, 0xb6, 0x07, 0x2c,
0x48, 0xa9, 0x10, 0xe6, 0x84, 0x33, 0xc7, 0xdc, 0x56, 0x3d, 0x88, 0xe3, 0x80, 0x53, 0x4b, 0xa1,
0xbd, 0xac, 0x6f, 0x5d, 0xa7, 0x24, 0x49, 0x68, 0x2a, 0xf2, 0xe2, 0xad, 0x47, 0x77, 0xf3, 0x42,
0xa6, 0x99, 0x2f, 0xf3, 0xec, 0xce, 0x9f, 0x45, 0x80, 0x2f, 0x44, 0x84, 0x36, 0xcf, 0x02, 0x16,
0x21, 0x1d, 0xca, 0x59, 0xca, 0x71, 0xa9, 0xa1, 0x19, 0x55, 0x67, 0x74, 0x44, 0x9b, 0x50, 0x11,
0x03, 0xd2, 0x7c, 0x79, 0x80, 0xcb, 0x2a, 0x58, 0xdc, 0x90, 0x0b, 0x1b, 0x2c, 0x24, 0x01, 0xf5,
0x92, 0x8c, 0x73, 0x2f, 0x89, 0x39, 0xf3, 0x87, 0x78, 0xb1, 0xa1, 0x19, 0xeb, 0xcd, 0x67, 0xe6,
0x1c, 0xbd, 0xa6, 0x9d, 0x71, 0x6e, 0x2b, 0xdc, 0xb9, 0xa7, 0x3a, 0x4c, 0x02, 0x68, 0x77, 0xa6,
0xa9, 0xa0, 0x7e, 0x4a, 0x25, 0x5e, 0x52, 0x73, 0x27, 0xac, 0xab, 0xc2, 0xe8, 0x39, 0xe8, 0x57,
0x34, 0x65, 0x7d, 0xe6, 0x13, 0xc9, 0xe2, 0xc8, 0xfb, 0x46, 0x87, 0xb8, 0x92, 0xa3, 0xd3, 0xf1,
0xf7, 0x74, 0x88, 0x5e, 0xc3, 0x5a, 0xa2, 0xfc, 0x79, 0x7e, 0x1c, 0xf5, 0x59, 0x80, 0x97, 0x1b,
0x9a, 0x51, 0x6b, 0x3e, 0x34, 0xf3, 0xd5, 0x98, 0xe3, 0xd5, 0x98, 0xae, 0x5a, 0x8d, 0xb3, 0x9a,
0xd3, 0xc7, 0x0a, 0x46, 0x8f, 0xa1, 0x56, 0x54, 0x47, 0x24, 0xa4, 0x78, 0x45, 0xcd, 0x80, 0x3c,
0xd4, 0x25, 0x21, 0x45, 0x87, 0xb0, 0x94, 0x0c, 0x88, 0xa0, 0xb8, 0xaa, 0xec, 0x1b, 0xf3, 0xed,
0xab, 0x3a, 0x7b, 0xc4, 0x3b, 0x79, 0x19, 0x7a, 0x05, 0x2b, 0x49, 0xca, 0xe2, 0x94, 0xc9, 0x21,
0x06, 0xa5, 0x6c, 0xfb, 0x1f, 0x65, 0x9d, 0x48, 0x1e, 0xec, 0x7f, 0x26, 0x3c, 0xa3, 0xce, 0x2d,
0x8c, 0x0e, 0x61, 0xfd, 0x92, 0xf6, 0x49, 0xc6, 0xe5, 0xd8, 0x18, 0x9d, 0x6f, 0x6c, 0xad, 0xc0,
0x0b, 0x67, 0xef, 0xa0, 0x16, 0x12, 0xe9, 0x0f, 0xbc, 0x34, 0xe3, 0x54, 0xe0, 0x7e, 0xa3, 0x6c,
0xd4, 0x9a, 0x4f, 0xe7, 0xca, 0xff, 0x38, 0xe2, 0x9d, 0x8c, 0x53, 0x07, 0xc2, 0xf1, 0x51, 0xa0,
0x7d, 0xd8, 0x9c, 0x15, 0xe2, 0x5d, 0x32, 0x41, 0x7a, 0x9c, 0xe2, 0xa0, 0xa1, 0x19, 0x2b, 0xce,
0xfd, 0x99, 0xb9, 0x27, 0x79, 0x6e, 0xe7, 0x87, 0x06, 0xd5, 0xdb, 0x7e, 0x08, 0xc3, 0x32, 0x8b,
0xd4, 0x60, 0xac, 0x35, 0xca, 0x46, 0xd5, 0x19, 0x5f, 0x47, 0x4f, 0xf0, 0x32, 0x0e, 0x09, 0x8b,
0x70, 0x49, 0x25, 0x8a, 0x1b, 0xb2, 0xa0, 0x52, 0xd8, 0x2e, 0xcf, 0xb7, 0x5d, 0x60, 0xe8, 0x09,
0xac, 0xdf, 0x91, 0xb7, 0xa8, 0xe4, 0xad, 0xf9, 0xd3, 0xba, 0x76, 0xdb, 0x50, 0x9b, 0xfa, 0x4a,
0xe8, 0x01, 0x6c, 0x9c, 0x77, 0x5d, 0xbb, 0x7d, 0xdc, 0x79, 0xdb, 0x69, 0x9f, 0x78, 0xf6, 0x69,
0xcb, 0x6d, 0xeb, 0x0b, 0xa8, 0x0a, 0x4b, 0xad, 0xf3, 0xb3, 0xd3, 0xae, 0xae, 0x8d, 0x8f, 0x17,
0x7a, 0x69, 0x74, 0x74, 0xcf, 0x5a, 0x67, 0xae, 0x5e, 0xde, 0x3d, 0x02, 0x98, 0x7a, 0xda, 0x9b,
0x80, 0x66, 0xba, 0x7c, 0xfa, 0xd0, 0x39, 0xfe, 0xaa, 0x2f, 0x20, 0x1d, 0x56, 0x3b, 0xfd, 0x6e,
0x2c, 0xed, 0x94, 0x0a, 0x1a, 0x49, 0x5d, 0x43, 0x00, 0x95, 0x16, 0xbf, 0x26, 0x43, 0xa1, 0x97,
0x8e, 0xde, 0xfc, 0xbc, 0xa9, 0x6b, 0xbf, 0x6e, 0xea, 0xda, 0xef, 0x9b, 0xba, 0x76, 0xd1, 0x0c,
0x98, 0x1c, 0x64, 0x3d, 0xd3, 0x8f, 0x43, 0x8b, 0x70, 0xd6, 0x23, 0x3d, 0x62, 0x15, 0x1f, 0xcb,
0x22, 0x09, 0xb3, 0xfe, 0xf3, 0x1b, 0xe9, 0x55, 0xd4, 0x32, 0x5e, 0xfc, 0x0d, 0x00, 0x00, 0xff,
0xff, 0x48, 0x74, 0xbe, 0xc1, 0x64, 0x04, 0x00, 0x00,
// 619 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x94, 0xdd, 0x4e, 0xdb, 0x4c,
0x10, 0x86, 0x71, 0x02, 0x81, 0x4c, 0x80, 0xcf, 0xac, 0xbe, 0xd2, 0x15, 0x54, 0x69, 0x84, 0xd4,
0xd6, 0xe5, 0xc0, 0x16, 0xa1, 0x3f, 0x27, 0x15, 0x6a, 0x80, 0xb4, 0x44, 0x6d, 0x53, 0xcb, 0x86,
0x56, 0xe5, 0xc4, 0xda, 0x98, 0x8d, 0xb3, 0xea, 0xfa, 0x47, 0xde, 0x35, 0x34, 0x17, 0xd2, 0x7b,
0xea, 0x61, 0x2f, 0xa1, 0xe2, 0x2e, 0x7a, 0x56, 0x65, 0x6d, 0x43, 0x42, 0xab, 0x9c, 0xed, 0xce,
0x3c, 0x33, 0xf3, 0xbe, 0xe3, 0x95, 0xa1, 0x49, 0xbf, 0x49, 0x1a, 0x09, 0x16, 0x47, 0xc2, 0xba,
0xdc, 0x23, 0x3c, 0x19, 0x91, 0x3d, 0xeb, 0x8a, 0x88, 0xd0, 0x4c, 0xd2, 0x58, 0xc6, 0x68, 0x7b,
0xc4, 0x82, 0x94, 0x0a, 0x61, 0xde, 0x72, 0x66, 0xc9, 0x6d, 0x35, 0x83, 0x38, 0x0e, 0x38, 0xb5,
0x14, 0x3a, 0xc8, 0x86, 0xd6, 0x55, 0x4a, 0x92, 0x84, 0xa6, 0x22, 0x2f, 0xde, 0x7a, 0x70, 0x37,
0x2f, 0x64, 0x9a, 0xf9, 0x32, 0xcf, 0xee, 0xfc, 0x5e, 0x04, 0xf8, 0x4c, 0x44, 0x68, 0xf3, 0x2c,
0x60, 0x11, 0xd2, 0xa1, 0x9a, 0xa5, 0x1c, 0x57, 0x5a, 0x9a, 0x51, 0x77, 0x26, 0x47, 0xb4, 0x09,
0x35, 0x31, 0x22, 0xed, 0xe7, 0x2f, 0x70, 0x55, 0x05, 0x8b, 0x1b, 0x72, 0x61, 0x83, 0x85, 0x24,
0xa0, 0x5e, 0x92, 0x71, 0xee, 0x25, 0x31, 0x67, 0xfe, 0x18, 0x2f, 0xb6, 0x34, 0x63, 0xbd, 0xfd,
0xc4, 0x9c, 0xa3, 0xd7, 0xb4, 0x33, 0xce, 0x6d, 0x85, 0x3b, 0xff, 0xa9, 0x0e, 0xb7, 0x01, 0xb4,
0x3b, 0xd3, 0x54, 0x50, 0x3f, 0xa5, 0x12, 0x2f, 0xa9, 0xb9, 0xb7, 0xac, 0xab, 0xc2, 0xe8, 0x29,
0xe8, 0x97, 0x34, 0x65, 0x43, 0xe6, 0x13, 0xc9, 0xe2, 0xc8, 0xfb, 0x4a, 0xc7, 0xb8, 0x96, 0xa3,
0xd3, 0xf1, 0x77, 0x74, 0x8c, 0x5e, 0xc1, 0x5a, 0xa2, 0xfc, 0x79, 0x7e, 0x1c, 0x0d, 0x59, 0x80,
0x97, 0x5b, 0x9a, 0xd1, 0x68, 0xdf, 0x37, 0xf3, 0xd5, 0x98, 0xe5, 0x6a, 0x4c, 0x57, 0xad, 0xc6,
0x59, 0xcd, 0xe9, 0x23, 0x05, 0xa3, 0x87, 0xd0, 0x28, 0xaa, 0x23, 0x12, 0x52, 0xbc, 0xa2, 0x66,
0x40, 0x1e, 0xea, 0x93, 0x90, 0xa2, 0x03, 0x58, 0x4a, 0x46, 0x44, 0x50, 0x5c, 0x57, 0xf6, 0x8d,
0xf9, 0xf6, 0x55, 0x9d, 0x3d, 0xe1, 0x9d, 0xbc, 0x0c, 0xbd, 0x84, 0x95, 0x24, 0x65, 0x71, 0xca,
0xe4, 0x18, 0x83, 0x52, 0xb6, 0xfd, 0x97, 0xb2, 0x5e, 0x24, 0xf7, 0xdb, 0x9f, 0x08, 0xcf, 0xa8,
0x73, 0x03, 0xa3, 0x03, 0x58, 0xbf, 0xa0, 0x43, 0x92, 0x71, 0x59, 0x1a, 0xa3, 0xf3, 0x8d, 0xad,
0x15, 0x78, 0xe1, 0xec, 0x2d, 0x34, 0x42, 0x22, 0xfd, 0x91, 0x97, 0x66, 0x9c, 0x0a, 0x3c, 0x6c,
0x55, 0x8d, 0x46, 0xfb, 0xf1, 0x5c, 0xf9, 0x1f, 0x26, 0xbc, 0x93, 0x71, 0xea, 0x40, 0x58, 0x1e,
0x05, 0x7a, 0x06, 0x9b, 0xb3, 0x42, 0xbc, 0x0b, 0x26, 0xc8, 0x80, 0x53, 0x1c, 0xb4, 0x34, 0x63,
0xc5, 0xf9, 0x7f, 0x66, 0xee, 0x71, 0x9e, 0xdb, 0xf9, 0xae, 0x41, 0xfd, 0xa6, 0x1f, 0xc2, 0xb0,
0xcc, 0x22, 0x35, 0x18, 0x6b, 0xad, 0xaa, 0x51, 0x77, 0xca, 0xeb, 0xe4, 0x09, 0x5e, 0xc4, 0x21,
0x61, 0x11, 0xae, 0xa8, 0x44, 0x71, 0x43, 0x16, 0xd4, 0x0a, 0xdb, 0xd5, 0xf9, 0xb6, 0x0b, 0x0c,
0x3d, 0x82, 0xf5, 0x3b, 0xf2, 0x16, 0x95, 0xbc, 0x35, 0x7f, 0x5a, 0xd7, 0x6e, 0x17, 0x1a, 0x53,
0x5f, 0x09, 0xdd, 0x83, 0x8d, 0xb3, 0xbe, 0x6b, 0x77, 0x8f, 0x7a, 0x6f, 0x7a, 0xdd, 0x63, 0xcf,
0x3e, 0xe9, 0xb8, 0x5d, 0x7d, 0x01, 0xd5, 0x61, 0xa9, 0x73, 0x76, 0x7a, 0xd2, 0xd7, 0xb5, 0xf2,
0x78, 0xae, 0x57, 0x26, 0x47, 0xf7, 0xb4, 0x73, 0xea, 0xea, 0xd5, 0xdd, 0x43, 0x80, 0xa9, 0xa7,
0xbd, 0x09, 0x68, 0xa6, 0xcb, 0xc7, 0xf7, 0xbd, 0xa3, 0x2f, 0xfa, 0x02, 0xd2, 0x61, 0xb5, 0x37,
0xec, 0xc7, 0xd2, 0x4e, 0xa9, 0xa0, 0x91, 0xd4, 0x35, 0x04, 0x50, 0xeb, 0xf0, 0x2b, 0x32, 0x16,
0x7a, 0xe5, 0xf0, 0xf5, 0x8f, 0xeb, 0xa6, 0xf6, 0xf3, 0xba, 0xa9, 0xfd, 0xba, 0x6e, 0x6a, 0xe7,
0xed, 0x80, 0xc9, 0x51, 0x36, 0x30, 0xfd, 0x38, 0xb4, 0x08, 0x67, 0x03, 0x32, 0x20, 0x56, 0xf1,
0xb1, 0x2c, 0x92, 0x30, 0xeb, 0x1f, 0xbf, 0x91, 0x41, 0x4d, 0x2d, 0x63, 0xff, 0x4f, 0x00, 0x00,
0x00, 0xff, 0xff, 0xb9, 0xf2, 0x67, 0xbe, 0x64, 0x04, 0x00, 0x00,
}
func (m *WasmPlugin) Marshal() (dAtA []byte, err error) {
@@ -1024,7 +1024,7 @@ func (m *WasmPlugin) Unmarshal(dAtA []byte) error {
return io.ErrUnexpectedEOF
}
if m.Priority == nil {
m.Priority = &types.Int64Value{}
m.Priority = &types.Int32Value{}
}
if err := m.Priority.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
return err

View File

@@ -98,7 +98,7 @@ message WasmPlugin {
// If `priority` is not set, or two `WasmPlugins` exist with the same
// value, the ordering will be deterministically derived from name and
// namespace of the `WasmPlugins`. Defaults to `0`.
google.protobuf.Int64Value priority = 10;
google.protobuf.Int32Value priority = 10;
// Extended by Higress, the default configuration takes effect globally
google.protobuf.Struct default_config = 101;

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 1.3.5
appVersion: 1.3.6
description: Helm chart for deploying higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -10,4 +10,4 @@ name: higress-core
sources:
- http://github.com/alibaba/higress
type: application
version: 1.3.5
version: 1.3.6

View File

@@ -70,6 +70,8 @@ spec:
periodSeconds: 3
timeoutSeconds: 5
env:
- name: HIGRESS_SYSTEM_NS
value: "{{ .Release.Namespace }}"
- name: DEFAULT_UPSTREAM_CONCURRENCY_THRESHOLD
value: "{{ .Values.global.defaultUpstreamConcurrencyThreshold }}"
- name: ISTIO_GPRC_MAXRECVMSGSIZE

View File

@@ -1,9 +1,9 @@
dependencies:
- name: higress-core
repository: file://../core
version: 1.3.5
version: 1.3.6
- name: higress-console
repository: https://higress.io/helm-charts/
version: 1.3.3
digest: sha256:ce2dba66d3b961eceb2de8bde4f271b06ae9bf677bda2c3bb621c51d29b76c71
generated: "2024-03-04T19:04:15.731018+08:00"
digest: sha256:6bf02020df81c81fedf69976ba0e2a2620527b7cbd11d7602e5e6ae3427b959f
generated: "2024-04-22T19:32:07.927664+08:00"

View File

@@ -1,5 +1,5 @@
apiVersion: v2
appVersion: 1.3.5
appVersion: 1.3.6
description: Helm chart for deploying Higress gateways
icon: https://higress.io/img/higress_logo_small.png
home: http://higress.io/
@@ -12,9 +12,9 @@ sources:
dependencies:
- name: higress-core
repository: "file://../core"
version: 1.3.5
version: 1.3.6
- name: higress-console
repository: "https://higress.io/helm-charts/"
version: 1.3.3
type: application
version: 1.3.5
version: 1.3.6

View File

@@ -0,0 +1,56 @@
diff -Naur istio/pilot/pkg/config/kube/gateway/conversion.go istio-new/pilot/pkg/config/kube/gateway/conversion.go
--- istio/pilot/pkg/config/kube/gateway/conversion.go 2024-03-08 17:23:49.000000000 +0800
+++ istio-new/pilot/pkg/config/kube/gateway/conversion.go 2024-03-08 17:02:50.000000000 +0800
@@ -16,6 +16,7 @@
import (
"fmt"
+ "path"
"regexp"
"sort"
"strconv"
@@ -28,6 +29,7 @@
gatewayapiV1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
istio "istio.io/api/networking/v1alpha3"
+ "istio.io/istio/pilot/pkg/features"
"istio.io/istio/pilot/pkg/model"
"istio.io/istio/pilot/pkg/model/credentials"
"istio.io/istio/pilot/pkg/model/kstatus"
@@ -290,6 +292,16 @@
return ret
}
+// Added by ingress
+func generateRouteName(obj config.Config) string {
+ if obj.Namespace == features.HigressSystemNs {
+ return obj.Name
+ }
+ return path.Join(obj.Namespace, obj.Name)
+}
+
+// End added by ingress
+
func buildHTTPVirtualServices(ctx *KubernetesResources, obj config.Config, gateways map[parentKey]map[gatewayapiV1beta1.SectionName]*parentInfo, gatewayRoutes map[string]map[string]*config.Config, domain string) {
route := obj.Spec.(*gatewayapiV1beta1.HTTPRouteSpec)
@@ -307,7 +319,7 @@
for _, r := range route.Rules {
// TODO: implement rewrite, timeout, mirror, corspolicy, retries
vs := &istio.HTTPRoute{
- Name: obj.Name,
+ Name: generateRouteName(obj),
}
for _, match := range r.Matches {
uri, err := createURIMatch(match)
diff -Naur istio/pilot/pkg/features/pilot.go istio-new/pilot/pkg/features/pilot.go
--- istio/pilot/pkg/features/pilot.go 2024-03-08 17:23:49.000000000 +0800
+++ istio-new/pilot/pkg/features/pilot.go 2024-03-08 17:00:05.000000000 +0800
@@ -577,6 +577,7 @@
"If enabled, the on demand filter will be added to the HCM filters").Get()
DefaultUpstreamConcurrencyThreshold = env.RegisterIntVar("DEFAULT_UPSTREAM_CONCURRENCY_THRESHOLD", 1000000,
"The default threshold of max_requests/max_pending_requests/max_connections of circuit breaker").Get()
+ HigressSystemNs = env.RegisterStringVar("HIGRESS_SYSTEM_NS", "higress-system", "The system namespace of Higress").Get()
// End added by ingress
)

View File

@@ -0,0 +1,20 @@
diff -Naur istio/pilot/pkg/networking/core/v1alpha3/gateway.go istio-new/pilot/pkg/networking/core/v1alpha3/gateway.go
--- istio/pilot/pkg/networking/core/v1alpha3/gateway.go 2024-04-22 18:08:26.000000000 +0800
+++ istio-new/pilot/pkg/networking/core/v1alpha3/gateway.go 2024-04-22 18:07:46.000000000 +0800
@@ -581,13 +581,13 @@
continue
}
if len(virtualService.Spec.(*networking.VirtualService).Hosts) > 1 {
- copiedVS := &networking.VirtualService{}
- copiedVS = virtualService.Spec.(*networking.VirtualService)
+ copiedVS := networking.VirtualService{}
+ copiedVS = *(virtualService.Spec.(*networking.VirtualService))
copiedVS.Hosts = []string{selectHost}
selectedVirtualServices = append(selectedVirtualServices, virtualServiceContext{
virtualService: config.Config{
Meta: virtualService.Meta,
- Spec: copiedVS,
+ Spec: &copiedVS,
Status: virtualService.Status,
},
server: vsCtx.server,

View File

@@ -24,14 +24,13 @@ import (
"runtime"
"strings"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/flags"
types2 "github.com/docker/docker/api/types"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/types"
"github.com/alibaba/higress/pkg/cmd/hgctl/docker"
"github.com/alibaba/higress/pkg/cmd/hgctl/kubernetes"
"github.com/alibaba/higress/pkg/cmd/options"
)
var (
@@ -55,7 +54,9 @@ var (
proxyAdminPort int
docker = false
project = "higress"
dockerCli = false
)
const (
@@ -107,7 +108,7 @@ func newDashboardCmd() *cobra.Command {
consoleCmd := consoleDashCmd()
consoleCmd.PersistentFlags().IntVar(&consolePort, "ui-port", defaultConsolePort, "The component dashboard UI port.")
consoleCmd.PersistentFlags().BoolVar(&docker, "docker", false, "Search higress console from docker")
consoleCmd.PersistentFlags().BoolVar(&dockerCli, "docker", false, "Search higress console from docker")
dashboardCmd.AddCommand(consoleCmd)
controllerDebugCmd := controllerDebugCmd()
@@ -165,23 +166,23 @@ func consoleDashCmd() *cobra.Command {
hgctl dash console
hgctl d console`,
RunE: func(cmd *cobra.Command, args []string) error {
if docker {
return accessDocker(cmd)
if dockerCli {
return accessDockerCompose(cmd)
}
client, err := kubernetes.NewCLIClient(options.DefaultConfigFlags.ToRawKubeConfigLoader())
if err != nil {
fmt.Printf("build kubernetes CLI client fail: %v\ntry to access docker container\n", err)
return accessDocker(cmd)
return accessDockerCompose(cmd)
}
pl, err := client.PodsForSelector(addonNamespace, "app.kubernetes.io/name=higress-console")
if err != nil {
fmt.Printf("build kubernetes CLI client fail: %v\ntry to access docker container\n", err)
return accessDocker(cmd)
return accessDockerCompose(cmd)
}
if len(pl.Items) < 1 {
fmt.Printf("no higress console pods found\ntry to access docker container\n")
return accessDocker(cmd)
return accessDockerCompose(cmd)
}
// only use the first pod in the list
@@ -193,27 +194,26 @@ func consoleDashCmd() *cobra.Command {
return cmd
}
// accessDocker access docker container
func accessDocker(cmd *cobra.Command) error {
dockerCli, err := command.NewDockerCli(command.WithCombinedStreams(os.Stdout))
// accessDockerCompose access docker compose ps
func accessDockerCompose(cmd *cobra.Command) error {
cli, err := docker.NewCompose(cmd.OutOrStdout())
if err != nil {
return fmt.Errorf("build docker CLI client fail: %w", err)
return errors.Wrap(err, "failed to build the docker compose client")
}
err = dockerCli.Initialize(flags.NewClientOptions())
list, err := cli.Ps(context.TODO(), project)
if err != nil {
return fmt.Errorf("docker client initialize fail: %w", err)
return errors.Wrap(err, "failed to build the docker compose ps")
}
apiClient := dockerCli.Client()
list, err := apiClient.ContainerList(context.Background(), types2.ContainerListOptions{})
for _, container := range list {
for i, name := range container.Names {
if strings.Contains(name, "higress-console") {
port := container.Ports[i].PublicPort
// not support define ip address
url := fmt.Sprintf("http://localhost:%d", port)
if strings.Contains(container.Service, "console") {
// not support define ip address
if container.Publishers != nil {
url := fmt.Sprintf("http://localhost:%d", container.Publishers[0].PublishedPort)
openBrowser(url, cmd.OutOrStdout(), browser)
return nil
}
return nil
}
}
return errors.New("no higress console container found")

View File

@@ -109,3 +109,7 @@ func (c Compose) List(ctx context.Context) ([]api.Stack, error) {
func (c Compose) Down(ctx context.Context, name string) error {
return c.client.Down(ctx, name, api.DownOptions{})
}
func (c Compose) Ps(ctx context.Context, name string) ([]api.ContainerSummary, error) {
return c.client.Ps(ctx, name, api.PsOptions{})
}

View File

@@ -50,7 +50,7 @@ func IsObject(typ string) bool {
var (
ErrInvalidModel = errors.New("invalid model")
ErrInvalidFiledType = errors.New("invalid field type")
ErrInvalidFieldType = errors.New("invalid field type")
)
type ModelParser struct {
@@ -248,24 +248,24 @@ func (p *ModelParser) parseModelFields(model string) (fields []Model, err error)
if field.Tag != nil {
ignore, err := fd.setTag(field.Tag.Value)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse tag %q of the field %q", fd.Tag, fd.Name)
return nil, errors.Wrapf(err, "failed to parse tag %q of the field %q", field.Tag, fd.Name)
}
if ignore {
continue
}
}
fd.Type, err = p.parseFiledType(pkgName, field.Type)
fd.Type, err = p.parseFieldType(pkgName, field.Type)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to parse type %q of the field %q", field.Type, fd.Name)
}
if IsObject(fd.Type) {
subModel, err := p.getModelName(field.Type)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to get the sub-model name of the field %q with type %q", fd.Name, field.Type)
}
fd.Fields, err = p.parseModelFields(subModel)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "failed to parse sub-model of the field %q with type %q", fd.Name, field.Type)
}
}
fields = append(fields, fd)
@@ -316,25 +316,25 @@ func (p *ModelParser) doGetModelName(pkgName string, typ ast.Expr) (string, erro
case *ast.SelectorExpr: // <pkg_name>.<field_name>
pkg, ok := t.X.(*ast.Ident)
if !ok {
return "", ErrInvalidFiledType
return "", ErrInvalidFieldType
}
pName := pkg.Name + "."
return p.doGetModelName(pName, t.Sel)
case *ast.Ident:
return pkgName + t.Name, nil
default:
return "", ErrInvalidFiledType
return "", ErrInvalidFieldType
}
}
func (p *ModelParser) parseFiledType(pkgName string, typ ast.Expr) (string, error) {
func (p *ModelParser) parseFieldType(pkgName string, typ ast.Expr) (string, error) {
switch t := typ.(type) {
case *ast.StructType: // nested struct
return string(JsonTypeObject), nil
case *ast.StarExpr: // *int -> int
return p.parseFiledType(pkgName, t.X)
return p.parseFieldType(pkgName, t.X)
case *ast.ArrayType: // slice or array
ret, err := p.parseFiledType(pkgName, t.Elt)
ret, err := p.parseFieldType(pkgName, t.Elt)
if err != nil {
return "", err
}
@@ -342,22 +342,22 @@ func (p *ModelParser) parseFiledType(pkgName string, typ ast.Expr) (string, erro
case *ast.SelectorExpr: // <pkg_name>.<field_name>
pkg, ok := t.X.(*ast.Ident)
if !ok {
return "", ErrInvalidFiledType
return "", ErrInvalidFieldType
}
pName := pkg.Name + "."
return p.parseFiledType(pName, t.Sel)
return p.parseFieldType(pName, t.Sel)
case *ast.Ident:
fName := pkgName + t.Name
if _, ok := p.structs[fName]; ok {
return string(JsonTypeObject), nil
}
if alias, ok := p.alias[fName]; ok {
return p.parseFiledType(pkgName, alias.expr)
return p.parseFieldType(pkgName, alias.expr)
}
jsonType, err := convert2JsonType(t.Name)
return string(jsonType), err
default:
return "", ErrInvalidFiledType
return "", ErrInvalidFieldType
}
}
@@ -375,7 +375,7 @@ func convert2JsonType(typ string) (JsonType, error) {
case "struct":
return JsonTypeObject, nil
default:
return "", ErrInvalidFiledType
return "", ErrInvalidFieldType
}
}

View File

@@ -798,7 +798,9 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
PluginConfig: obj.PluginConfig,
PluginName: obj.PluginName,
Phase: extensions.PluginPhase(obj.Phase),
Priority: obj.Priority,
}
if obj.GetPriority() != nil {
result.Priority = &types.Int64Value{Value: int64(obj.GetPriority().Value)}
}
if result.PluginConfig != nil {
return result, nil

View File

@@ -48,13 +48,15 @@ func V1Available(client kube.Client) bool {
serverVersion, err := client.GetKubernetesVersion()
if err != nil {
return false
// Consider the new ingress package is available as default
return true
}
runningVersion, err := version.ParseGeneric(serverVersion.String())
if err != nil {
// Consider the new ingress package is available as default
IngressLog.Errorf("unexpected error parsing running Kubernetes version: %v", err)
return false
return true
}
return runningVersion.AtLeast(version119)

View File

@@ -38,6 +38,7 @@ const (
maxInitialConnectionWindowSize = 2147483647
defaultIdleTimeout = 180
defaultRouteTimeout = 0
defaultUpStreamIdleTimeout = 10
defaultUpStreamConnectionBufferLimits = 10485760
defaultMaxRequestHeadersKb = 60
@@ -67,6 +68,8 @@ type Downstream struct {
ConnectionBufferLimits uint32 `json:"connectionBufferLimits,omitempty"`
// Http2 configures HTTP/2 specific options.
Http2 *Http2 `json:"http2,omitempty"`
//RouteTimeout limits the time that timeout for the route.
RouteTimeout uint32 `json:"routeTimeout"`
}
// Upstream configures the behavior of the upstream connection.
@@ -158,6 +161,7 @@ func deepCopyGlobal(global *Global) (*Global, error) {
newGlobal.Downstream.Http2.InitialStreamWindowSize = global.Downstream.Http2.InitialStreamWindowSize
newGlobal.Downstream.Http2.InitialConnectionWindowSize = global.Downstream.Http2.InitialConnectionWindowSize
}
newGlobal.Downstream.RouteTimeout = global.Downstream.RouteTimeout
}
if global.Upstream != nil {
newGlobal.Upstream.IdleTimeout = global.Upstream.IdleTimeout
@@ -185,6 +189,7 @@ func NewDefaultDownstream() *Downstream {
MaxRequestHeadersKb: defaultMaxRequestHeadersKb,
ConnectionBufferLimits: defaultConnectionBufferLimits,
Http2: NewDefaultHttp2(),
RouteTimeout: defaultRouteTimeout,
}
}
@@ -308,8 +313,7 @@ func (g *GlobalOptionController) ConstructEnvoyFilters() ([]*config.Config, erro
configPatch := make([]*networking.EnvoyFilter_EnvoyConfigObjectPatch, 0)
global := g.GetGlobal()
if global == nil {
configs := make([]*config.Config, 0)
return configs, nil
return []*config.Config{}, nil
}
namespace := g.Namespace
@@ -326,31 +330,29 @@ func (g *GlobalOptionController) ConstructEnvoyFilters() ([]*config.Config, erro
configPatch = append(configPatch, disableXEnvoyHeadersConfig...)
}
if global.Downstream == nil {
return generateEnvoyFilter(namespace, configPatch), nil
if global.Downstream != nil {
downstreamStruct := g.constructDownstream(global.Downstream)
bufferLimitStruct := g.constructBufferLimit(global.Downstream)
routeTimeoutStruct := g.constructRouteTimeout(global.Downstream)
downstreamConfig := g.generateDownstreamEnvoyFilter(downstreamStruct, bufferLimitStruct, routeTimeoutStruct, namespace)
if downstreamConfig != nil {
configPatch = append(configPatch, downstreamConfig...)
}
}
downstreamStruct := g.constructDownstream(global.Downstream)
bufferLimitStruct := g.constructBufferLimit(global.Downstream)
if len(downstreamStruct) == 0 && len(bufferLimitStruct) == 0 {
return generateEnvoyFilter(namespace, configPatch), nil
if global.Upstream != nil {
upstreamStruct := g.constructUpstream(global.Upstream)
bufferLimitStruct := g.constructUpstreamBufferLimit(global.Upstream)
upstreamConfig := g.generateUpstreamEnvoyFilter(upstreamStruct, bufferLimitStruct, namespace)
if upstreamConfig != nil {
configPatch = append(configPatch, upstreamConfig...)
}
}
downstreamConfig := g.generateDownstreamEnvoyFilter(downstreamStruct, bufferLimitStruct, namespace)
configPatch = append(configPatch, downstreamConfig...)
if global.Upstream == nil {
return generateEnvoyFilter(namespace, configPatch), nil
if len(configPatch) == 0 {
return []*config.Config{}, nil
}
upstreamStruct := g.constructUpstream(global.Upstream)
bufferLimitStruct = g.constructUpstreamBufferLimit(global.Upstream)
if len(upstreamStruct) == 0 {
return generateEnvoyFilter(namespace, configPatch), nil
}
upstreamConfig := g.generateUpstreamEnvoyFilter(upstreamStruct, bufferLimitStruct, namespace)
configPatch = append(configPatch, upstreamConfig...)
return generateEnvoyFilter(namespace, configPatch), nil
}
@@ -375,9 +377,11 @@ func (g *GlobalOptionController) RegisterItemEventHandler(eventHandler ItemEvent
}
// generateDownstreamEnvoyFilter generates the downstream envoy filter.
func (g *GlobalOptionController) generateDownstreamEnvoyFilter(downstreamValueStruct string, bufferLimitStruct string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch {
downstreamConfig := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
func (g *GlobalOptionController) generateDownstreamEnvoyFilter(downstreamValueStruct string, bufferLimitStruct string, routeTimeoutStruct string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch {
var downstreamConfig []*networking.EnvoyFilter_EnvoyConfigObjectPatch
if len(downstreamValueStruct) != 0 {
downstreamConfig = append(downstreamConfig, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_NETWORK_FILTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
@@ -395,8 +399,11 @@ func (g *GlobalOptionController) generateDownstreamEnvoyFilter(downstreamValueSt
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(downstreamValueStruct),
},
},
{
})
}
if len(bufferLimitStruct) != 0 {
downstreamConfig = append(downstreamConfig, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_LISTENER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
@@ -405,14 +412,39 @@ func (g *GlobalOptionController) generateDownstreamEnvoyFilter(downstreamValueSt
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(bufferLimitStruct),
},
},
})
}
if len(routeTimeoutStruct) != 0 {
downstreamConfig = append(downstreamConfig, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_HTTP_ROUTE,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_RouteConfiguration{
RouteConfiguration: &networking.EnvoyFilter_RouteConfigurationMatch{
Vhost: &networking.EnvoyFilter_RouteConfigurationMatch_VirtualHostMatch{
Route: &networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch{
Action: networking.EnvoyFilter_RouteConfigurationMatch_RouteMatch_ROUTE,
},
},
},
},
},
Patch: &networking.EnvoyFilter_Patch{
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(routeTimeoutStruct),
},
})
}
return downstreamConfig
}
func (g *GlobalOptionController) generateUpstreamEnvoyFilter(upstreamValueStruct string, bufferLimit string, namespace string) []*networking.EnvoyFilter_EnvoyConfigObjectPatch {
upstreamConfig := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{
{
var upstreamConfig []*networking.EnvoyFilter_EnvoyConfigObjectPatch
if len(upstreamValueStruct) != 0 {
upstreamConfig = append(upstreamConfig, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_CLUSTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
@@ -421,8 +453,11 @@ func (g *GlobalOptionController) generateUpstreamEnvoyFilter(upstreamValueStruct
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(upstreamValueStruct),
},
},
{
})
}
if len(bufferLimit) != 0 {
upstreamConfig = append(upstreamConfig, &networking.EnvoyFilter_EnvoyConfigObjectPatch{
ApplyTo: networking.EnvoyFilter_CLUSTER,
Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{
Context: networking.EnvoyFilter_GATEWAY,
@@ -431,8 +466,9 @@ func (g *GlobalOptionController) generateUpstreamEnvoyFilter(upstreamValueStruct
Operation: networking.EnvoyFilter_Patch_MERGE,
Value: util.BuildPatchStruct(bufferLimit),
},
},
})
}
return upstreamConfig
}
@@ -597,3 +633,14 @@ func (g *GlobalOptionController) constructBufferLimit(downstream *Downstream) st
}
`, downstream.ConnectionBufferLimits)
}
// constructRouteTimeout constructs the route timeout config.
func (g *GlobalOptionController) constructRouteTimeout(downstream *Downstream) string {
return fmt.Sprintf(`
{
"route": {
"timeout": "%ds"
}
}
`, downstream.RouteTimeout)
}

View File

@@ -29,7 +29,6 @@ build-image:
--build-arg BUILDER=${BUILDER} \
--build-arg GOPROXY=$(GOPROXY) \
-t ${IMG} \
--load \
.
@echo ""
@echo "image: ${IMG}"
@@ -72,4 +71,4 @@ local-run:
python3 .devcontainer/gen_config.py ${PLUGIN_NAME}
envoy -c extensions/${PLUGIN_NAME}/config.yaml --concurrency 0 --log-level info --component-log-level wasm:debug
local-all: local-build local-run
local-all: local-build local-run

View File

@@ -0,0 +1,28 @@
# 功能说明
`cache-control`插件实现了基于 URL 文件后缀来为请求的响应头部添加 `Expires``Cache-Control` 头部,从而方便浏览器对特定后缀的文件进行缓存,例如 `jpg``png` 等图片文件。
# 配置字段
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|---------|--------|-----------------------------------------------------------------------------------------------------|-|--------------------------|
| suffix | string | 选填,表示匹配的文件后缀名,例如 `jpg``png` 等。<br/>如果需要匹配多种后缀,需要用 `\|` 进行分割,例如 `png\|jpg`。<br/>如果不填写,表示匹配所有后缀 | - | 配置用于匹配的请求文件后缀 |
| expires | string | 必填,表示缓存的最长时间。<br/>当填入的字符串为数字时单位为秒例如需要缓存1小时需填写 3600。<br/>另外,还可以填写 epoch 或 max<br/>,与 nginx 中语义相同。 | - | 配置缓存的最大时间 |
# 配置示例
1. 缓存后缀为 `jpg`, `png`, `jpeg` 的文件,缓存时间为一小时
```yaml
suffix: jpg|png|jpeg
expires: 3600
```
根据该配置,下列请求在访问时,将会在响应头中添加 `Expires``Cache-Control` 字段,且过期时间为 1 小时后。
```bash
curl http://example.com/test.png
curl http://exmaple.com/test.jpg
```
2. 缓存所有文件,且缓存至最大时间 `“Thu, 31 Dec 2037 23:55:55 GMT”`
```yaml
expires: max
```

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,20 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/cache-control
go 1.18
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
github.com/tidwall/gjson v1.14.3
)
require (
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
)

View File

@@ -0,0 +1,86 @@
package main
import (
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
"net/http"
"strconv"
"strings"
"time"
)
func main() {
wrapper.SetCtx(
"cache-control",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders),
wrapper.ProcessResponseHeadersBy(onHttpResponseHeaders),
)
}
type CacheControlConfig struct {
suffix []string
expires string
}
func parseConfig(json gjson.Result, config *CacheControlConfig, log wrapper.Log) error {
suffix := json.Get("suffix").String()
if suffix != "" {
parts := strings.Split(suffix, "|")
config.suffix = parts
}
config.expires = json.Get("expires").String()
log.Infof("suffix: %q, expires: %s", config.suffix, config.expires)
return nil
}
func onHttpRequestHeaders(ctx wrapper.HttpContext, config CacheControlConfig, log wrapper.Log) types.Action {
path := ctx.Path()
if strings.Contains(path, "?") {
path = strings.Split(path, "?")[0]
}
ctx.SetContext("path", path)
log.Debugf("path: %s", path)
return types.ActionContinue
}
func onHttpResponseHeaders(ctx wrapper.HttpContext, config CacheControlConfig, log wrapper.Log) types.Action {
hit := false
if len(config.suffix) == 0 {
hit = true
} else {
path, ok := ctx.GetContext("path").(string)
if !ok {
log.Error("failed to get request path")
return types.ActionContinue
}
for _, part := range config.suffix {
if strings.HasSuffix(path, "."+part) {
hit = true
break
}
}
}
if hit {
if config.expires == "max" {
proxywasm.AddHttpResponseHeader("Expires", "Thu, 31 Dec 2037 23:55:55 GMT")
proxywasm.AddHttpResponseHeader("Cache-Control", "maxAge=315360000")
} else if config.expires == "epoch" {
proxywasm.AddHttpResponseHeader("Expires", "Thu, 01 Jan 1970 00:00:01 GMT")
proxywasm.AddHttpResponseHeader("Cache-Control", "no-cache")
} else {
maxAge, _ := strconv.ParseInt(config.expires, 10, 64)
currentTime := time.Now()
expireTime := currentTime.Add(time.Duration(maxAge) * time.Second)
proxywasm.AddHttpResponseHeader("Expires", expireTime.UTC().Format(http.TimeFormat))
proxywasm.AddHttpResponseHeader("Cache-Control", "maxAge="+strconv.FormatInt(maxAge, 10))
}
}
return types.ActionContinue
}

View File

@@ -0,0 +1,31 @@
# 功能说明
`ip-restriction `插件可以通过将 IP 地址列入白名单或黑名单来限制对服务或路由的访问.支持对单个 IP 地址、多个 IP 地址和类似
10.10.10.0/24 的 CIDR范围的限制.
# 配置说明
| 配置项 | 类型 | 必填 | 默认值 | 说明 |
|----------------|--------|----|-----------------------------|------------------------------------------|
| ip_source_type | string | 否 | origin-source | 可选值1. 对端socket ip`origin-source`; 2. 通过header获取`header` |
| ip_header_name | string | 否 | x-forwarded-for | 当`ip_source_type``header`指定自定义IP来源头 |
| allow | array | 否 | [] | 白名单列表 |
| deny | array | 否 | [] | 黑名单列表 |
| status | int | 否 | 403 | 拒绝访问时的 HTTP 状态码 |
| message | string | 否 | Your IP address is blocked. | 拒绝访问时的返回信息 |
```yaml
ip_source_type: origin-source
allow:
- 10.0.0.1
- 192.168.0.0/16
```
```yaml
ip_source_type: header
ip_header_name: x-real-iP
deny:
- 10.0.0.1
- 192.169.0.0/16
```

View File

@@ -0,0 +1 @@
1.0.0

View File

@@ -0,0 +1,22 @@
module github.com/alibaba/higress/plugins/wasm-go/extensions/ip-restriction
go 1.19
replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
github.com/tidwall/gjson v1.14.3
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837
)
require (
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect
github.com/magefile/mage v1.14.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/tidwall/resp v0.1.1 // indirect
)

View File

@@ -0,0 +1,24 @@
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56 h1:Wi5Tgn8K+jDcBYL+dIMS1+qXYH2r7tpRAyBgqrWfQtw=
github.com/asergeyev/nradix v0.0.0-20170505151046-3872ab85bb56/go.mod h1:8BhOLuqtSuT5NZtZMwfvEibi09RO3u79uqfHZzfDTR4=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw=
github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/resp v0.1.1 h1:Ly20wkhqKTmDUPlyM1S7pWo5kk0tDu8OoC/vFArXmwE=
github.com/tidwall/resp v0.1.1/go.mod h1:3/FrruOBAxPTPtundW0VXgmsQ4ZBA0Aw714lVYgwFa0=
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837 h1:DjHnADS2r2zynZ3WkCFAQ+PNYngMSNceRROi0pO6c3M=
github.com/zmap/go-iptree v0.0.0-20210731043055-d4e632617837/go.mod h1:9vp0bxqozzQwcjBwenEXfKVq8+mYbwHkQ1NF9Ap0DMw=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

View File

@@ -0,0 +1,157 @@
package main
import (
"encoding/json"
"fmt"
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
"github.com/tidwall/gjson"
"github.com/zmap/go-iptree/iptree"
"net"
"strings"
)
const (
DefaultRealIpHeader string = "X-Forwarded-For"
DefaultDenyStatus uint32 = 403
DefaultDenyMessage string = "Your IP address is blocked."
)
const (
OriginSourceType = "origin-source"
HeaderSourceType = "header"
)
type RestrictionConfig struct {
IPSourceType string `json:"ip_source_type"` //IP来源类型
IPHeaderName string `json:"ip_header_name"` //真实IP头
Allow *iptree.IPTree `json:"allow"` //允许的IP
Deny *iptree.IPTree `json:"deny"` //拒绝的IP
Status uint32 `json:"status"` //被拒绝时返回的状态码
Message string `json:"message"` //被拒绝时返回的消息
}
func main() {
wrapper.SetCtx(
"ip-restriction",
wrapper.ParseConfigBy(parseConfig),
wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders))
}
func parseConfig(json gjson.Result, config *RestrictionConfig, log wrapper.Log) error {
sourceType := json.Get("ip_source_type")
if sourceType.Exists() && sourceType.String() != "" {
switch sourceType.String() {
case HeaderSourceType:
config.IPSourceType = HeaderSourceType
case OriginSourceType:
default:
config.IPSourceType = OriginSourceType
}
} else {
config.IPSourceType = OriginSourceType
}
header := json.Get("ip_header_name")
if header.Exists() && header.String() != "" {
config.IPHeaderName = header.String()
} else {
config.IPHeaderName = DefaultRealIpHeader
}
status := json.Get("status")
if status.Exists() && status.Uint() > 1 {
config.Status = uint32(header.Uint())
} else {
config.Status = DefaultDenyStatus
}
message := json.Get("message")
if message.Exists() && message.String() != "" {
config.Message = message.String()
} else {
config.Message = DefaultDenyMessage
}
allowNets, err := parseIPNets(json.Get("allow").Array())
if err != nil {
log.Error(err.Error())
return err
}
denyNets, err := parseIPNets(json.Get("deny").Array())
if err != nil {
log.Error(err.Error())
return err
}
if allowNets != nil && denyNets != nil {
log.Warn("allow and deny cannot be set at the same time")
return fmt.Errorf("allow and deny cannot be set at the same time")
}
if allowNets == nil && denyNets == nil {
log.Warn("allow and deny cannot be empty at the same time")
return fmt.Errorf("allow and deny cannot be empty at the same time")
}
config.Allow = allowNets
config.Deny = denyNets
return nil
}
func getDownStreamIp(config RestrictionConfig) (net.IP, error) {
var (
s string
err error
)
if config.IPSourceType == HeaderSourceType {
s, err = proxywasm.GetHttpRequestHeader(config.IPHeaderName)
if err == nil {
s = strings.Split(strings.Trim(s, " "), ",")[0]
}
} else {
var bs []byte
bs, err = proxywasm.GetProperty([]string{"source", "address"})
s = string(bs)
}
if err != nil {
return nil, err
}
ip := parseIP(s)
realIP := net.ParseIP(ip)
if realIP == nil {
return nil, fmt.Errorf("invalid ip[%s]", ip)
}
return realIP, nil
}
func onHttpRequestHeaders(context wrapper.HttpContext, config RestrictionConfig, log wrapper.Log) types.Action {
realIp, err := getDownStreamIp(config)
if err != nil {
return deniedUnauthorized(config)
}
allow := config.Allow
deny := config.Deny
if allow != nil {
if realIp == nil {
log.Error("realIp is nil, blocked")
return deniedUnauthorized(config)
}
if _, found, _ := allow.Get(realIp); !found {
return deniedUnauthorized(config)
}
}
if deny != nil {
if realIp == nil {
log.Error("realIp is nil, continue")
return types.ActionContinue
}
if _, found, _ := deny.Get(realIp); found {
return deniedUnauthorized(config)
}
}
return types.ActionContinue
}
func deniedUnauthorized(config RestrictionConfig) types.Action {
body, _ := json.Marshal(map[string]string{
"message": config.Message,
})
_ = proxywasm.SendHttpResponse(config.Status, nil, body, -1)
return types.ActionContinue
}

View File

@@ -0,0 +1,37 @@
package main
import (
"fmt"
"github.com/tidwall/gjson"
"github.com/zmap/go-iptree/iptree"
"strings"
)
// parseIPNets 解析Ip段配置
func parseIPNets(array []gjson.Result) (*iptree.IPTree, error) {
if len(array) == 0 {
return nil, nil
} else {
tree := iptree.New()
for _, result := range array {
err := tree.AddByString(result.String(), 0)
if err != nil {
return nil, fmt.Errorf("invalid IP[%s]", result.String())
}
}
return tree, nil
}
}
// parseIP 解析IP
func parseIP(source string) string {
if strings.Contains(source, ".") {
// parse ipv4
return strings.Split(source, ":")[0]
}
//parse ipv6
if strings.Contains(source, "]") {
return strings.Split(source, "]")[0][1:]
}
return source
}

View File

@@ -0,0 +1,106 @@
package main
import (
"github.com/tidwall/gjson"
"testing"
)
func Test_parseIPNets(t *testing.T) {
type args struct {
array []gjson.Result
}
tests := []struct {
name string
args args
wantVal bool
wantErr bool
}{
{
name: "",
args: args{
array: gjson.Parse(`["127.0.0.1/30","10.0.0.1"]`).Array(),
},
wantVal: true,
wantErr: false,
},
{
name: "",
args: args{
array: gjson.Parse(``).Array(),
},
wantVal: false,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := parseIPNets(tt.args.array)
if (err != nil) != tt.wantErr {
t.Errorf("parseIPNets() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !tt.wantVal && got == nil {
return
}
if _, found, _ := got.GetByString("10.0.0.1"); found != tt.wantVal {
t.Errorf("parseIPNets() got = %v, want %v", found, tt.wantVal)
return
}
})
}
}
func Test_parseIP(t *testing.T) {
type args struct {
source string
}
tests := []struct {
name string
args args
want string
}{
// TODO: Add test cases.
{
name: "case 1",
args: args{
"127.0.0.1",
},
want: "127.0.0.1",
},
{
name: "case 2",
args: args{
"127.0.0.1:12",
},
want: "127.0.0.1",
},
{
name: "case 3",
args: args{
"fe80::14d5:8aff:fed9:2114",
},
want: "fe80::14d5:8aff:fed9:2114",
},
{
name: "case 4",
args: args{
"[fe80::14d5:8aff:fed9:2114]:123",
},
want: "fe80::14d5:8aff:fed9:2114",
},
{
name: "case 5",
args: args{
"127.0.0.1:12,[fe80::14d5:8aff:fed9:2114]:123",
},
want: "127.0.0.1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := parseIP(tt.args.source); got != tt.want {
t.Errorf("parseIP() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -502,3 +502,95 @@ $ curl -v -X POST console.higress.io/post \
...
}
```
# 特殊用法实现基于Body参数路由
**Note**
> 需要数据面的proxy wasm版本大于等于0.2.100
> 编译时需要带上版本的tag例如`tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags="custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100" ./`
配置示例:
```yaml
reqRules:
- operate: map
headers:
- fromKey: userId
toKey: x-user-id
mapSource: body
```
此规则将请求body中的`userId`解析出后设置到请求Header`x-user-id`中这样就可以基于Higress请求Header匹配路由的能力来实现基于Body参数的路由了。
此配置同时支持`application/json`和`application/x-www-form-urlencoded`两种类型的请求Body。
举例来说:
**对于application/json类型的body**
```bash
curl localhost -d '{"userId":12, "userName":"johnlanni"}' -H 'content-type:application/json'
```
将从json中提取出`userId`字段的值,设置到`x-user-id`中,后端服务收到的请求头将增加:`x-usr-id: 12`。
因为在插件新增这个Header后网关将重新计算路由所以可以实现网关路由配置根据这个请求头来匹配路由到特定的目标服务。
**对于application/x-www-form-urlencoded类型的body**
```bash
curl localhost -d 'userId=12&userName=johnlanni'
```
将从`k1=v1&k2=v2`这样的表单格式中提取出`userId`字段的值,设置到`x-user-id`中,后端服务收到的请求头将增加:`x-usr-id: 12`。
因为在插件新增这个Header后网关将重新计算路由所以可以实现网关路由配置根据这个请求头来匹配路由到特定的目标服务。
## json path 支持
可以根据 [GJSON Path 语法](https://github.com/tidwall/gjson/blob/master/SYNTAX.md),从复杂的 json 中提取出字段。
比较常用的操作举例,对于以下 json:
```json
{
"name": {"first": "Tom", "last": "Anderson"},
"age":37,
"children": ["Sara","Alex","Jack"],
"fav.movie": "Deer Hunter",
"friends": [
{"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]},
{"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]},
{"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]}
]
}
```
可以实现这样的提取:
```text
name.last "Anderson"
name.first "Tom"
age 37
children ["Sara","Alex","Jack"]
children.0 "Sara"
children.1 "Alex"
friends.1 {"first": "Roger", "last": "Craig", "age": 68}
friends.1.first "Roger"
```
现在如果想从上面这个 json 格式的 body 中提取出 friends 中第二项的 first 字段,来设置到 Header `x-first-name` 中,同时抽取 last 字段,来设置到 Header `x-last-name` 中,则可以使用这份插件配置:
```yaml
reqRules:
- operate: map
headers:
- fromKey: friends.1.first
toKey: x-first-name
- fromKey: friends.1.last
toKey: x-last-name
mapSource: body
```

View File

@@ -6,7 +6,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../..
require (
github.com/alibaba/higress/plugins/wasm-go v0.0.0-20230829022308-8747e1ddadf0
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.8.4
github.com/tidwall/gjson v1.17.0

View File

@@ -4,8 +4,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbGQ2DTIXHBHxWfqCYQW1fKjyJ/I7W1pMyUDeEA=
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc h1:t2AT8zb6N/59Y78lyRWedVoVWHNRSCBh0oWCC+bluTQ=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240327114451-d6b7174a84fc/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=

View File

@@ -243,6 +243,8 @@ func parseConfig(json gjson.Result, config *TransformerConfig, log wrapper.Log)
return errors.Wrapf(err, "failed to new transformer")
}
log.Infof("transform config is: reqRules:%+v, respRules:%+v", config.reqRules, config.respRules)
return nil
}
@@ -312,11 +314,17 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config TransformerConfig, log
if hs["content-type"] != nil {
contentType = hs["content-type"][0]
}
if config.reqTrans.IsBodyChange() && isValidRequestContentType(contentType) {
ctx.SetContext("content-type", contentType)
isValidRequestContent := isValidRequestContentType(contentType)
isBodyChange := config.reqTrans.IsBodyChange()
needBodyMapSource := config.reqTrans.NeedBodyMapSource()
log.Debugf("contentType:%s, isValidRequestContent:%v, isBodyChange:%v, needBodyMapSource:%v",
contentType, isValidRequestContent, isBodyChange, needBodyMapSource)
if isBodyChange && isValidRequestContent {
delete(hs, "content-length")
ctx.SetContext("content-type", contentType)
} else {
ctx.DontReadRequestBody()
}
qs, err := parseQueryByPath(path)
@@ -328,26 +336,26 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config TransformerConfig, log
ctx.SetContext("headers", hs)
ctx.SetContext("querys", qs)
var mapSourceData MapSourceData
switch config.reqTrans.GetMapSource() {
case "headers":
mapSourceData.mapSourceType = "headers"
mapSourceData.kvs = hs
case "querys":
mapSourceData.mapSourceType = "querys"
mapSourceData.kvs = qs
case "self":
if !isValidRequestContent || (!isBodyChange && !needBodyMapSource) {
ctx.DontReadRequestBody()
} else if needBodyMapSource {
// we need do transform during body phase
ctx.SetContext("need_head_trans", struct{}{})
log.Debug("delay header's transform to body phase")
return types.HeaderStopIteration
}
default:
log.Warnf("invalid mapSource in request header: %v", config.reqTrans.GetMapSource())
return types.ActionContinue
mapSourceData := make(map[string]MapSourceData)
mapSourceData["headers"] = MapSourceData{
mapSourceType: "headers",
kvs: hs,
}
mapSourceData["querys"] = MapSourceData{
mapSourceType: "querys",
kvs: qs,
}
if config.reqTrans.IsHeaderChange() {
if config.reqTrans.GetMapSource() == "self" {
mapSourceData.mapSourceType = "headers"
mapSourceData.kvs = hs
}
if err = config.reqTrans.TransformHeaders(host, path, hs, mapSourceData); err != nil {
log.Warnf("failed to transform request headers: %v", err)
return types.ActionContinue
@@ -355,10 +363,6 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config TransformerConfig, log
}
if config.reqTrans.IsQueryChange() {
if config.reqTrans.GetMapSource() == "self" {
mapSourceData.mapSourceType = "querys"
mapSourceData.kvs = qs
}
if err = config.reqTrans.TransformQuerys(host, path, qs, mapSourceData); err != nil {
log.Warnf("failed to transform request query params: %v", err)
return types.ActionContinue
@@ -381,7 +385,7 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config TransformerConfig, log
}
func onHttpRequestBody(ctx wrapper.HttpContext, config TransformerConfig, body []byte, log wrapper.Log) types.Action {
if config.reqTrans == nil || !config.reqTrans.IsBodyChange() {
if config.reqTrans == nil {
return types.ActionContinue
}
@@ -406,51 +410,80 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config TransformerConfig, body [
return types.ActionContinue
}
var mapSourceData MapSourceData
mapSourceData := make(map[string]MapSourceData)
var hs map[string][]string
var qs map[string][]string
switch config.reqTrans.GetMapSource() {
case "headers":
{
hs = ctx.GetContext("headers").(map[string][]string)
if hs == nil {
log.Warn("failed to get request headers")
hs = ctx.GetContext("headers").(map[string][]string)
if hs == nil {
log.Warn("failed to get request headers")
return types.ActionContinue
}
if hs[":authority"] == nil {
log.Warn(errGetRequestHost.Error())
return types.ActionContinue
}
if hs[":path"] == nil {
log.Warn(errGetRequestPath.Error())
return types.ActionContinue
}
mapSourceData["headers"] = MapSourceData{
mapSourceType: "headers",
kvs: hs,
}
qs = ctx.GetContext("querys").(map[string][]string)
if qs == nil {
log.Warn("failed to get request querys")
return types.ActionContinue
}
mapSourceData["querys"] = MapSourceData{
mapSourceType: "querys",
kvs: qs,
}
switch structuredBody.(type) {
case map[string]interface{}:
mapSourceData["body"] = MapSourceData{
mapSourceType: "bodyJson",
json: structuredBody.(map[string]interface{})["body"].([]byte),
}
case map[string][]string:
mapSourceData["body"] = MapSourceData{
mapSourceType: "bodyKv",
kvs: structuredBody.(map[string][]string),
}
}
if ctx.GetContext("need_head_trans") != nil {
if config.reqTrans.IsHeaderChange() {
if err = config.reqTrans.TransformHeaders(host, path, hs, mapSourceData); err != nil {
log.Warnf("failed to transform request headers: %v", err)
return types.ActionContinue
}
if hs[":authority"] == nil {
log.Warn(errGetRequestHost.Error())
return types.ActionContinue
}
if hs[":path"] == nil {
log.Warn(errGetRequestPath.Error())
return types.ActionContinue
}
mapSourceData.mapSourceType = "headers"
mapSourceData.kvs = hs
}
case "querys":
{
qs = ctx.GetContext("querys").(map[string][]string)
if qs == nil {
log.Warn("failed to get request querys")
if config.reqTrans.IsQueryChange() {
if err = config.reqTrans.TransformQuerys(host, path, qs, mapSourceData); err != nil {
log.Warnf("failed to transform request query params: %v", err)
return types.ActionContinue
}
mapSourceData.mapSourceType = "querys"
mapSourceData.kvs = qs
path, err = constructPath(path, qs)
if err != nil {
log.Warnf("failed to construct path: %v", err)
return types.ActionContinue
}
hs[":path"] = []string{path}
}
case "body", "self":
switch structuredBody.(type) {
case map[string]interface{}:
mapSourceData.mapSourceType = "bodyJson"
mapSourceData.json = structuredBody.(map[string]interface{})["body"].([]byte)
case map[string][]string:
mapSourceData.mapSourceType = "bodyKv"
mapSourceData.kvs = structuredBody.(map[string][]string)
headers := reconvertHeaders(hs)
if err = proxywasm.ReplaceHttpRequestHeaders(headers); err != nil {
log.Warnf("failed to replace request headers: %v", err)
return types.ActionContinue
}
default:
log.Warnf("invalid mapSource in request body: %v", config.reqTrans.GetMapSource())
}
if !config.reqTrans.IsBodyChange() {
return types.ActionContinue
}
@@ -495,21 +528,28 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, config TransformerConfig, lo
if hs["content-type"] != nil {
contentType = hs["content-type"][0]
}
if config.respTrans.IsBodyChange() && isValidResponseContentType(contentType) {
ctx.SetContext("content-type", contentType)
isValidResponseContent := isValidResponseContentType(contentType)
isBodyChange := config.respTrans.IsBodyChange()
needBodyMapSource := config.respTrans.NeedBodyMapSource()
if isBodyChange && isValidResponseContent {
delete(hs, "content-length")
ctx.SetContext("content-type", contentType)
} else {
ctx.DontReadResponseBody()
}
var mapSourceData MapSourceData
switch config.respTrans.GetMapSource() {
case "headers", "self":
mapSourceData.mapSourceType = "headers"
mapSourceData.kvs = hs
default:
log.Warnf("invalid mapSource in response header: %v", config.respTrans.GetMapSource())
return types.ActionContinue
if !isValidResponseContent || (!isBodyChange && !needBodyMapSource) {
ctx.DontReadResponseBody()
} else if needBodyMapSource {
// we need do transform during body phase
ctx.SetContext("need_head_trans", struct{}{})
return types.HeaderStopIteration
}
mapSourceData := make(map[string]MapSourceData)
mapSourceData["headers"] = MapSourceData{
mapSourceType: "headers",
kvs: hs,
}
if config.respTrans.IsHeaderChange() {
@@ -529,7 +569,7 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, config TransformerConfig, lo
}
func onHttpResponseBody(ctx wrapper.HttpContext, config TransformerConfig, body []byte, log wrapper.Log) types.Action {
if config.respTrans == nil || !config.respTrans.IsBodyChange() {
if config.respTrans == nil {
return types.ActionContinue
}
@@ -554,30 +594,48 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config TransformerConfig, body
return types.ActionContinue
}
var mapSourceData MapSourceData
switch config.respTrans.GetMapSource() {
case "headers":
{
hs := ctx.GetContext("headers").(map[string][]string)
if hs == nil {
log.Warn("failed to get response headers")
mapSourceData := make(map[string]MapSourceData)
var hs map[string][]string
hs = ctx.GetContext("headers").(map[string][]string)
if hs == nil {
log.Warn("failed to get response headers")
return types.ActionContinue
}
mapSourceData["headers"] = MapSourceData{
mapSourceType: "headers",
kvs: hs,
}
switch structuredBody.(type) {
case map[string]interface{}:
mapSourceData["body"] = MapSourceData{
mapSourceType: "bodyJson",
json: structuredBody.(map[string]interface{})["body"].([]byte),
}
case map[string][]string:
mapSourceData["body"] = MapSourceData{
mapSourceType: "bodyKv",
kvs: structuredBody.(map[string][]string),
}
}
if ctx.GetContext("need_head_trans") != nil {
if config.respTrans.IsHeaderChange() {
if err = config.respTrans.TransformHeaders(host, path, hs, mapSourceData); err != nil {
log.Warnf("failed to transform response headers: %v", err)
return types.ActionContinue
}
mapSourceData.mapSourceType = "headers"
mapSourceData.kvs = hs
}
case "body", "self":
switch structuredBody.(type) {
case map[string]interface{}:
mapSourceData.mapSourceType = "bodyJson"
mapSourceData.json = structuredBody.(map[string]interface{})["body"].([]byte)
case map[string][]string:
mapSourceData.mapSourceType = "bodyKv"
mapSourceData.kvs = structuredBody.(map[string][]string)
headers := reconvertHeaders(hs)
if err = proxywasm.ReplaceHttpResponseHeaders(headers); err != nil {
log.Warnf("failed to replace response headers: %v", err)
return types.ActionContinue
}
default:
log.Warnf("invalid mapSource in response body: %v", config.respTrans.GetMapSource())
}
if !config.respTrans.IsBodyChange() {
return types.ActionContinue
}
@@ -657,36 +715,34 @@ func newTransformRule(rules []gjson.Result) (res []TransformRule, err error) {
}
type Transformer interface {
TransformHeaders(host, path string, hs map[string][]string, mapSourceData MapSourceData) error
TransformQuerys(host, path string, qs map[string][]string, mapSourceData MapSourceData) error
TransformBody(host, path string, body interface{}, mapSourceData MapSourceData) error
TransformHeaders(host, path string, hs map[string][]string, mapSourceData map[string]MapSourceData) error
TransformQuerys(host, path string, qs map[string][]string, mapSourceData map[string]MapSourceData) error
TransformBody(host, path string, body interface{}, mapSourceData map[string]MapSourceData) error
IsHeaderChange() bool
IsQueryChange() bool
IsBodyChange() bool
GetMapSource() string
NeedBodyMapSource() bool
}
var _ Transformer = (*requestTransformer)(nil)
var _ Transformer = (*responseTransformer)(nil)
type requestTransformer struct {
headerHandler *kvHandler
queryHandler *kvHandler
bodyHandler *requestBodyHandler
isHeaderChange bool
isQueryChange bool
isBodyChange bool
// 目前插件在对request做map转换的时候只支持最多一个映射来源
// 取值headersquerysbodyself
mapSource string
headerHandler *kvHandler
queryHandler *kvHandler
bodyHandler *requestBodyHandler
isHeaderChange bool
isQueryChange bool
isBodyChange bool
needBodyMapSource bool
}
func newRequestTransformer(config *TransformerConfig) (Transformer, error) {
headerKvtGroup, isHeaderChange, withHeaderMapKvt, err := newKvtGroup(config.reqRules, "headers")
headerKvtGroup, isHeaderChange, _, err := newKvtGroup(config.reqRules, "headers")
if err != nil {
return nil, errors.Wrap(err, "failed to new kvt group for headers")
}
queryKvtGroup, isQueryChange, withQueryMapKvt, err := newKvtGroup(config.reqRules, "querys")
queryKvtGroup, isQueryChange, _, err := newKvtGroup(config.reqRules, "querys")
if err != nil {
return nil, errors.Wrap(err, "failed to new kvt group for querys")
}
@@ -695,12 +751,7 @@ func newRequestTransformer(config *TransformerConfig) (Transformer, error) {
return nil, errors.Wrap(err, "failed to new kvt group for body")
}
mapSource := getMapSourceFromRule(config.reqRules)
// TODO: not support mapping headers or querys from body in requestTransformer before #582 is fixed
if mapSource == "body" && (withHeaderMapKvt || withQueryMapKvt) {
return nil, errors.Wrap(err, "not support mapping headers or querys from body in requestTransformer")
}
bodyMapSource := bodyMapSourceInRule(config.reqRules)
return &requestTransformer{
headerHandler: &kvHandler{headerKvtGroup},
@@ -709,22 +760,22 @@ func newRequestTransformer(config *TransformerConfig) (Transformer, error) {
formDataHandler: &kvHandler{bodyKvtGroup},
jsonHandler: &jsonHandler{bodyKvtGroup},
},
isHeaderChange: isHeaderChange,
isQueryChange: isQueryChange,
isBodyChange: isBodyChange,
mapSource: mapSource,
isHeaderChange: isHeaderChange,
isQueryChange: isQueryChange,
isBodyChange: isBodyChange,
needBodyMapSource: bodyMapSource,
}, nil
}
func (t requestTransformer) TransformHeaders(host, path string, hs map[string][]string, mapSourceData MapSourceData) error {
func (t requestTransformer) TransformHeaders(host, path string, hs map[string][]string, mapSourceData map[string]MapSourceData) error {
return t.headerHandler.handle(host, path, hs, mapSourceData)
}
func (t requestTransformer) TransformQuerys(host, path string, qs map[string][]string, mapSourceData MapSourceData) error {
func (t requestTransformer) TransformQuerys(host, path string, qs map[string][]string, mapSourceData map[string]MapSourceData) error {
return t.queryHandler.handle(host, path, qs, mapSourceData)
}
func (t requestTransformer) TransformBody(host, path string, body interface{}, mapSourceData MapSourceData) error {
func (t requestTransformer) TransformBody(host, path string, body interface{}, mapSourceData map[string]MapSourceData) error {
switch body.(type) {
case map[string][]string:
return t.bodyHandler.formDataHandler.handle(host, path, body.(map[string][]string), mapSourceData)
@@ -744,23 +795,22 @@ func (t requestTransformer) TransformBody(host, path string, body interface{}, m
return nil
}
func (t requestTransformer) IsHeaderChange() bool { return t.isHeaderChange }
func (t requestTransformer) IsQueryChange() bool { return t.isQueryChange }
func (t requestTransformer) IsBodyChange() bool { return t.isBodyChange }
func (t requestTransformer) GetMapSource() string { return t.mapSource }
func (t requestTransformer) IsHeaderChange() bool { return t.isHeaderChange }
func (t requestTransformer) IsQueryChange() bool { return t.isQueryChange }
func (t requestTransformer) IsBodyChange() bool { return t.isBodyChange }
func (t requestTransformer) NeedBodyMapSource() bool { return t.needBodyMapSource }
type responseTransformer struct {
headerHandler *kvHandler
bodyHandler *responseBodyHandler
isHeaderChange bool
isBodyChange bool
// 目前插件在对response做map转换的时候只支持最多一个映射来源
mapSource string
headerHandler *kvHandler
bodyHandler *responseBodyHandler
isHeaderChange bool
isBodyChange bool
needBodyMapSource bool
}
func newResponseTransformer(config *TransformerConfig) (Transformer, error) {
headerKvtGroup, isHeaderChange, withHeaderMapKvt, err := newKvtGroup(config.respRules, "headers")
headerKvtGroup, isHeaderChange, _, err := newKvtGroup(config.respRules, "headers")
if err != nil {
return nil, errors.Wrap(err, "failed to new kvt group for headers")
}
@@ -768,30 +818,27 @@ func newResponseTransformer(config *TransformerConfig) (Transformer, error) {
if err != nil {
return nil, errors.Wrap(err, "failed to new kvt group for body")
}
mapSource := getMapSourceFromRule(config.respRules)
// TODO: not support mapping headers from body in responseTransformer before #582 is fixed
if mapSource == "body" && withHeaderMapKvt {
return nil, errors.Wrap(err, "not support mapping headers from body in responseTransformer")
}
bodyMapSource := bodyMapSourceInRule(config.respRules)
return &responseTransformer{
headerHandler: &kvHandler{headerKvtGroup},
bodyHandler: &responseBodyHandler{&jsonHandler{bodyKvtGroup}},
isHeaderChange: isHeaderChange,
isBodyChange: isBodyChange,
mapSource: mapSource,
headerHandler: &kvHandler{headerKvtGroup},
bodyHandler: &responseBodyHandler{&jsonHandler{bodyKvtGroup}},
isHeaderChange: isHeaderChange,
isBodyChange: isBodyChange,
needBodyMapSource: bodyMapSource,
}, nil
}
func (t responseTransformer) TransformHeaders(host, path string, hs map[string][]string, mapSourceData MapSourceData) error {
func (t responseTransformer) TransformHeaders(host, path string, hs map[string][]string, mapSourceData map[string]MapSourceData) error {
return t.headerHandler.handle(host, path, hs, mapSourceData)
}
func (t responseTransformer) TransformQuerys(host, path string, qs map[string][]string, mapSourceData MapSourceData) error {
func (t responseTransformer) TransformQuerys(host, path string, qs map[string][]string, mapSourceData map[string]MapSourceData) error {
// the response does not need to transform the query params, always returns nil
return nil
}
func (t responseTransformer) TransformBody(host, path string, body interface{}, mapSourceData MapSourceData) error {
func (t responseTransformer) TransformBody(host, path string, body interface{}, mapSourceData map[string]MapSourceData) error {
switch body.(type) {
case map[string]interface{}:
m := body.(map[string]interface{})
@@ -808,10 +855,10 @@ func (t responseTransformer) TransformBody(host, path string, body interface{},
return nil
}
func (t responseTransformer) IsHeaderChange() bool { return t.isHeaderChange }
func (t responseTransformer) IsQueryChange() bool { return false } // the response does not need to transform the query params, always returns false
func (t responseTransformer) IsBodyChange() bool { return t.isBodyChange }
func (t responseTransformer) GetMapSource() string { return t.mapSource }
func (t responseTransformer) IsHeaderChange() bool { return t.isHeaderChange }
func (t responseTransformer) IsQueryChange() bool { return false } // the response does not need to transform the query params, always returns false
func (t responseTransformer) IsBodyChange() bool { return t.isBodyChange }
func (t responseTransformer) NeedBodyMapSource() bool { return t.needBodyMapSource }
type requestBodyHandler struct {
formDataHandler *kvHandler
@@ -830,7 +877,7 @@ type jsonHandler struct {
kvtOps []kvtOperation
}
func (h kvHandler) handle(host, path string, kvs map[string][]string, mapSourceData MapSourceData) error {
func (h kvHandler) handle(host, path string, kvs map[string][]string, mapSourceData map[string]MapSourceData) error {
// arbitary order. for example: remove → rename → replace → add → append → map → dedupe
for _, kvtOp := range h.kvtOps {
@@ -887,17 +934,29 @@ func (h kvHandler) handle(host, path string, kvs map[string][]string, mapSourceD
// map: 若指定 fromKey 不存在则无操作;否则将 fromKey 的值映射给 toKey 的值
for _, map_ := range kvtOp.mapKvtGroup {
fromKey, toKey := map_.fromKey, map_.toKey
if mapSourceData.mapSourceType == "headers" {
if kvtOp.mapSource == "headers" {
fromKey = strings.ToLower(fromKey)
}
if fromValue, ok := mapSourceData.search(fromKey); ok {
switch mapSourceData.mapSourceType {
source, exist := mapSourceData[kvtOp.mapSource]
if !exist {
proxywasm.LogWarnf("map key failed, source:%s not exists", kvtOp.mapSource)
continue
}
proxywasm.LogDebugf("search key:%s in source:%s", fromKey, kvtOp.mapSource)
if fromValue, ok := source.search(fromKey); ok {
switch source.mapSourceType {
case "headers", "querys", "bodyKv":
kvs[toKey] = fromValue.([]string)
// TODO: not support mapping headers or querys from body before #582 is fixed
// case "bodyJson":
// kvs[toKey] = fromValue
// }
proxywasm.LogDebugf("map key:%s to key:%s success, value is: %v", fromKey, toKey, fromValue)
case "bodyJson":
if valueJson, ok := fromValue.(gjson.Result); ok {
valueStr := valueJson.String()
if valueStr != "" {
kvs[toKey] = []string{valueStr}
proxywasm.LogDebugf("map key:%s to key:%s success, values is:%s", fromKey, toKey, valueStr)
}
}
}
}
}
@@ -938,7 +997,7 @@ func (h kvHandler) handle(host, path string, kvs map[string][]string, mapSourceD
}
// only for body
func (h jsonHandler) handle(host, path string, oriData []byte, mapSourceData MapSourceData) (data []byte, err error) {
func (h jsonHandler) handle(host, path string, oriData []byte, mapSourceData map[string]MapSourceData) (data []byte, err error) {
// arbitary order. for example: remove → rename → replace → add → append → map → dedupe
if !gjson.ValidBytes(oriData) {
return nil, errors.New("invalid json body")
@@ -1054,14 +1113,30 @@ func (h jsonHandler) handle(host, path string, oriData []byte, mapSourceData Map
// map: 若指定 fromKey 不存在则无操作;否则将 fromKey 的值映射给 toKey 的值
for _, map_ := range kvtOp.mapKvtGroup {
fromKey, toKey := map_.fromKey, map_.toKey
if mapSourceData.mapSourceType == "headers" {
if kvtOp.mapSource == "headers" {
fromKey = strings.ToLower(fromKey)
}
if fromValue, ok := mapSourceData.search(fromKey); ok {
// search返回的类型为[]string或者gjson.Result.Value()
// sjson.SetBytes()能够直接处理[]byte其他更复杂的数据类型均会json.Marshall化
if data, err = sjson.SetBytes(data, toKey, fromValue); err != nil {
return nil, errors.Wrap(err, errMap.Error())
source, exist := mapSourceData[kvtOp.mapSource]
if !exist {
proxywasm.LogWarnf("map key failed, source:%s not exists", kvtOp.mapSource)
continue
}
proxywasm.LogDebugf("search key:%s in source:%s", fromKey, kvtOp.mapSource)
if fromValue, ok := source.search(fromKey); ok {
switch source.mapSourceType {
case "headers", "querys", "bodyKv":
if data, err = sjson.SetBytes(data, toKey, fromValue); err != nil {
return nil, errors.Wrap(err, errMap.Error())
}
proxywasm.LogDebugf("map key:%s to key:%s success, value is: %v", fromKey, toKey, fromValue)
case "bodyJson":
if valueJson, ok := fromValue.(gjson.Result); ok {
if data, err = sjson.SetBytes(data, toKey, valueJson.Value()); err != nil {
return nil, errors.Wrap(err, errMap.Error())
}
proxywasm.LogDebugf("map key:%s to key:%s success, value is: %v", fromKey, toKey, fromValue)
}
}
}
}
@@ -1226,10 +1301,17 @@ func newKvtGroup(rules []TransformRule, typ string) (g []kvtOperation, isChange
kvtOp.renameKvtGroup = append(kvtOp.renameKvtGroup, renameKvt{p.renameParam.oldKey, p.renameParam.newKey, p.valueType})
case "map":
if typ == "headers" {
p.mapParam.fromKey = strings.ToLower(p.mapParam.fromKey)
p.mapParam.toKey = strings.ToLower(p.mapParam.toKey)
}
kvtOp.mapSource = r.mapSource
if kvtOp.mapSource == "self" {
kvtOp.mapSource = typ
r.mapSource = typ
}
if kvtOp.mapSource == "headers" {
p.mapParam.fromKey = strings.ToLower(p.mapParam.fromKey)
}
kvtOp.mapKvtGroup = append(kvtOp.mapKvtGroup, mapKvt{p.mapParam.fromKey, p.mapParam.toKey})
case "dedupe":
if typ == "headers" {
@@ -1301,20 +1383,19 @@ func (msdata MapSourceData) search(fromKey string) (interface{}, bool) {
if !fromValue.Exists() {
return nil, false
}
return fromValue.Value(), true
return fromValue, true
default:
return "", false
}
}
func getMapSourceFromRule(rules []TransformRule) string {
// 如果rules中不含map转换要求则返回空字符串
func bodyMapSourceInRule(rules []TransformRule) bool {
for _, r := range rules {
if r.operate == "map" {
return r.mapSource
if r.operate == "map" && r.mapSource == "body" {
return true
}
}
return ""
return false
}
type kvtReg struct {

View File

@@ -21,6 +21,7 @@ import (
"mime"
"mime/multipart"
"net/url"
"sort"
"strconv"
"strings"
@@ -263,5 +264,8 @@ func reconvertHeaders(hs map[string][]string) [][2]string {
ret = append(ret, [2]string{k, v})
}
}
sort.SliceStable(ret, func(i, j int) bool {
return ret[i][0] < ret[j][0]
})
return ret
}

View File

@@ -5,7 +5,7 @@ go 1.19
require (
github.com/google/uuid v1.3.0
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43
github.com/stretchr/testify v1.8.4
github.com/tidwall/gjson v1.14.3
github.com/tidwall/resp v0.1.1

View File

@@ -6,6 +6,8 @@ github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 h1:IHDghbG
github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520/go.mod h1:Nz8ORLaFiLWotg6GeKlJMhv8cci8mM43uEnLA5t8iew=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a h1:luYRvxLTE1xYxrXYj7nmjd1U0HHh8pUPiKfdZ0MhCGE=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240226064518-b3dc4646a35a/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43 h1:dCw7F/9ciw4NZN7w68wQRaygZ2zGOWMTIEoRvP1tlWs=
github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240318034951-d5306e367c43/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo=
github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo=
github.com/magefile/mage v1.14.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

View File

@@ -98,6 +98,7 @@ func (m *RuleMatcher[PluginConfig]) ParseRuleConfig(config gjson.Result,
if keyCount == 0 {
// enable globally for empty config
m.hasGlobalConfig = true
parsePluginConfig(config, &m.globalConfig)
return nil
}
if rulesJson, ok := obj[RULES_KEY]; ok {

View File

@@ -16,20 +16,22 @@ package wrapper
import (
"bytes"
"encoding/base64"
"fmt"
"io"
"github.com/google/uuid"
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
"github.com/tidwall/resp"
)
type RedisResponseCallback func(status int, response resp.Value)
type RedisResponseCallback func(response resp.Value)
type RedisClient interface {
Init(username, password string, timeout int64) error
// with this function, you can call redis as if you are using redis-cli
Command(cmds []interface{}, callback RedisResponseCallback) error
// BatchCommands(cmds [][]interface{}, callback func(status int, response []resp.Value)) error
Eval(script string, params []interface{}, callback RedisResponseCallback) error
Eval(script string, numkeys int, keys, args []interface{}, callback RedisResponseCallback) error
// Key
Del(key string, callback RedisResponseCallback) error
@@ -112,42 +114,53 @@ func RedisInit(cluster Cluster, username, password string, timeout uint32) error
return proxywasm.RedisInit(cluster.ClusterName(), username, password, timeout)
}
func RedisCall(cluster Cluster, respQuery string, callback RedisResponseCallback) error {
func RedisCall(cluster Cluster, respQuery []byte, callback RedisResponseCallback) error {
requestID := uuid.New().String()
_, err := proxywasm.DispatchRedisCall(
cluster.ClusterName(),
respQuery,
func(status, responseSize int) {
// proxywasm.LogCriticalf("[rinfx log] responseSize is: %d", responseSize)
func(status int, responseSize int) {
response, err := proxywasm.GetRedisCallResponse(0, responseSize)
if err != nil {
proxywasm.LogCriticalf("failed to get redis response body: %v", err)
var responseValue resp.Value
if status != 0 {
proxywasm.LogCriticalf("Error occured while calling redis, it seems cannot connect to the redis cluster. request-id: %s", requestID)
responseValue = resp.ErrorValue(fmt.Errorf("cannot connect to redis cluster"))
} else {
if err != nil {
proxywasm.LogCriticalf("failed to get redis response body, request-id: %s, error: %v", requestID, err)
responseValue = resp.ErrorValue(fmt.Errorf("cannot get redis response"))
} else {
rd := resp.NewReader(bytes.NewReader(response))
value, _, err := rd.ReadValue()
if err != nil && err != io.EOF {
proxywasm.LogCriticalf("failed to read redis response body, request-id: %s, error: %v", requestID, err)
responseValue = resp.ErrorValue(fmt.Errorf("cannot read redis response"))
} else {
responseValue = value
proxywasm.LogDebugf("redis call end, request-id: %s, respQuery: %s, respValue: %s",
requestID, base64.StdEncoding.EncodeToString([]byte(respQuery)), base64.StdEncoding.EncodeToString(response))
}
}
}
rd := resp.NewReader(bytes.NewReader(response))
v, _, err := rd.ReadValue()
if err != nil && err != io.EOF {
proxywasm.LogCriticalf("failed to read redis response body: %v", err)
}
// log.Infof("value: %s", v.String())
// callback(status, v.String())
callback(status, v)
callback(responseValue)
})
if err != nil {
proxywasm.LogCriticalf("redis call failed: %v", err)
proxywasm.LogCriticalf("redis call failed, request-id: %s, error: %v", requestID, err)
} else {
proxywasm.LogDebugf("redis call start, request-id: %s, respQuery: %s", requestID, base64.StdEncoding.EncodeToString([]byte(respQuery)))
}
return err
}
func respString(args []interface{}) string {
func respString(args []interface{}) []byte {
var buf bytes.Buffer
wr := resp.NewWriter(&buf)
arr := make([]resp.Value, 0)
for _, arg := range args {
// arr = append(arr, resp.AnyValue(arg))
arr = append(arr, resp.StringValue(fmt.Sprint(arg)))
}
wr.WriteArray(arr)
// proxywasm.LogCriticalf("respString:\n%s", buf.String())
return buf.String()
return buf.Bytes()
}
func (c RedisClusterClient[C]) Init(username, password string, timeout int64) error {
@@ -159,16 +172,17 @@ func (c RedisClusterClient[C]) Init(username, password string, timeout int64) er
}
func (c RedisClusterClient[C]) Command(cmds []interface{}, callback RedisResponseCallback) error {
RedisCall(c.cluster, respString(cmds), callback)
return nil
return RedisCall(c.cluster, respString(cmds), callback)
}
func (c RedisClusterClient[C]) Eval(script string, params []interface{}, callback RedisResponseCallback) error {
args := make([]interface{}, 0)
args = append(args, "eval")
args = append(args, script)
args = append(args, params...)
return RedisCall(c.cluster, respString(args), callback)
func (c RedisClusterClient[C]) Eval(script string, numkeys int, keys, args []interface{}, callback RedisResponseCallback) error {
params := make([]interface{}, 0)
params = append(params, "eval")
params = append(params, script)
params = append(params, numkeys)
params = append(params, keys...)
params = append(params, args...)
return RedisCall(c.cluster, respString(params), callback)
}
// Key
@@ -214,8 +228,7 @@ func (c RedisClusterClient[C]) Set(key string, value interface{}, callback Redis
args = append(args, "set")
args = append(args, key)
args = append(args, value)
RedisCall(c.cluster, respString(args), callback)
return nil
return RedisCall(c.cluster, respString(args), callback)
}
func (c RedisClusterClient[C]) SetEx(key string, value interface{}, ttl int, callback RedisResponseCallback) error {
@@ -224,8 +237,7 @@ func (c RedisClusterClient[C]) SetEx(key string, value interface{}, ttl int, cal
args = append(args, key)
args = append(args, ttl)
args = append(args, value)
RedisCall(c.cluster, respString(args), callback)
return nil
return RedisCall(c.cluster, respString(args), callback)
}
func (c RedisClusterClient[C]) MGet(keys []string, callback RedisResponseCallback) error {
@@ -234,8 +246,7 @@ func (c RedisClusterClient[C]) MGet(keys []string, callback RedisResponseCallbac
for _, k := range keys {
args = append(args, k)
}
RedisCall(c.cluster, respString(args), callback)
return nil
return RedisCall(c.cluster, respString(args), callback)
}
func (c RedisClusterClient[C]) MSet(kvMap map[string]interface{}, callback RedisResponseCallback) error {

View File

@@ -50,6 +50,7 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
InitialStreamWindowSize: 65535,
InitialConnectionWindowSize: 1048576,
},
RouteTimeout: 15,
},
Upstream: &configmap.Upstream{
IdleTimeout: 10,
@@ -121,6 +122,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
},
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "15s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -146,6 +155,7 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
InitialStreamWindowSize: 65535,
InitialConnectionWindowSize: 1048576,
},
RouteTimeout: 15,
},
Upstream: &configmap.Upstream{
IdleTimeout: 10,
@@ -209,6 +219,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
},
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "15s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -234,6 +252,7 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
InitialStreamWindowSize: 65535,
InitialConnectionWindowSize: 1048576,
},
RouteTimeout: 15,
},
Upstream: &configmap.Upstream{
IdleTimeout: 10,
@@ -302,6 +321,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
},
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "15s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -327,6 +354,7 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
InitialStreamWindowSize: 65535,
InitialConnectionWindowSize: 1048576,
},
RouteTimeout: 15,
},
Upstream: &configmap.Upstream{
IdleTimeout: 10,
@@ -387,6 +415,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
},
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "15s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -468,6 +504,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
"idle_timeout": "180s",
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "0s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -492,6 +536,7 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
InitialStreamWindowSize: 65535,
InitialConnectionWindowSize: 1048576,
},
RouteTimeout: 15,
},
DisableXEnvoyHeaders: true,
AddXRealIpHeader: true,
@@ -559,6 +604,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
},
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "15s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -584,6 +637,7 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
InitialStreamWindowSize: 65535,
InitialConnectionWindowSize: 1048576,
},
RouteTimeout: 60,
},
DisableXEnvoyHeaders: true,
AddXRealIpHeader: true,
@@ -651,6 +705,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
},
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "60s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -716,6 +778,14 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
"idle_timeout": "180s",
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "0s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
@@ -819,6 +889,106 @@ var ConfigMapGlobalEnvoy = suite.ConformanceTest{
},
},
},
{
name: "close the setting of route timeout in downstream",
higressConfig: &configmap.HigressConfig{
Downstream: &configmap.Downstream{
IdleTimeout: 180,
MaxRequestHeadersKb: 60,
ConnectionBufferLimits: 32768,
Http2: &configmap.Http2{
MaxConcurrentStreams: 100,
InitialStreamWindowSize: 65535,
InitialConnectionWindowSize: 1048576,
},
RouteTimeout: 0,
},
Upstream: &configmap.Upstream{
IdleTimeout: 10,
},
DisableXEnvoyHeaders: true,
AddXRealIpHeader: true,
},
envoyAssertion: []envoy.Assertion{
{
Path: "configs.#.dynamic_route_configs.#.route_config",
CheckType: envoy.CheckTypeExist,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"request_headers_to_add": []interface{}{
map[string]interface{}{
"append": false,
"header": map[string]interface{}{
"key": "x-real-ip",
"value": "%REQ(X-ENVOY-EXTERNAL-ADDRESS)%",
},
},
},
},
},
{
Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config",
CheckType: envoy.CheckTypeExist,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"@type": "type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager",
"stat_prefix": "outbound_0.0.0.0_80",
},
},
{
Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config.http_filters",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"name": "envoy.filters.http.router",
"typed_config": map[string]interface{}{
"@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router",
"suppress_envoy_headers": true,
},
},
},
{
Path: "configs.#.dynamic_listeners.#.active_state.listener",
CheckType: envoy.CheckTypeExist,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"per_connection_buffer_limit_bytes": 32768,
},
},
{
Path: "configs.#.dynamic_listeners.#.active_state.listener.filter_chains.#.filters.#.typed_config",
CheckType: envoy.CheckTypeExist,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"max_concurrent_streams": 100,
"initial_stream_window_size": 65535,
"initial_connection_window_size": 1048576,
"stream_idle_timeout": "180s",
"max_request_headers_kb": 60,
"idle_timeout": "180s",
},
},
{
Path: "configs.#.dynamic_route_configs.#.route_config.virtual_hosts.#.routes.#.route",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"timeout": "0s",
},
},
{
Path: "configs.#.dynamic_active_clusters.#.cluster",
CheckType: envoy.CheckTypeMatch,
TargetNamespace: "higress-system",
ExpectEnvoyConfig: map[string]interface{}{
"common_http_protocol_options": map[string]interface{}{
"idle_timeout": "10s",
},
"per_connection_buffer_limit_bytes": 10485760,
},
},
},
},
{
name: "close the setting of idle timeout in upstream",
higressConfig: &configmap.HigressConfig{

View File

@@ -0,0 +1,63 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package tests
import (
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
"testing"
)
func init() {
Register(WasmPluginCacheControl)
}
var WasmPluginCacheControl = suite.ConformanceTest{
ShortName: "WasmPluginCacheControl",
Description: "The Ingress in the higress-conformance-infra namespace test the cache control WASM Plugin",
Manifests: []string{"tests/go-wasm-cache-control.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TestCaseName: "case 1: Test hit",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/foo",
Headers: map[string]string{"User-Agent": "BaiduMobaider/1.1.0"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
Headers: map[string]string{
"Cache-Control": "maxAge=3600",
},
},
},
},
}
t.Run("WasmPlugins cache-control", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,48 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed 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.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
name: wasmplugin-cache-control
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Prefix
path: "/foo"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: cache-control
namespace: higress-system
spec:
defaultConfigDisable: true
matchRules:
- config:
expires: 3600
configDisable: false
ingress:
- higress-conformance-infra/wasmplugin-cache-control
url: file:///opt/plugins/wasm-go/extensions/cache-control/plugin.wasm

View File

@@ -0,0 +1,46 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed 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.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
name: httproute-app-root
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: ip-restriction-allow
namespace: higress-system
spec:
defaultConfig:
ip_source_type: header
ip_header_name: x-real-ip
allow:
- 192.168.0.1/16
- 10.0.0.1
url: file:///opt/plugins/wasm-go/extensions/ip-restriction/plugin.wasm

View File

@@ -0,0 +1,46 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed 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.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
name: httproute-app-root
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: extensions.higress.io/v1alpha1
kind: WasmPlugin
metadata:
name: ip-restriction-deny
namespace: higress-system
spec:
defaultConfig:
ip_source_type: header
ip_header_name: x-real-ip
deny:
- 192.168.0.1/16
- 10.0.0.1
url: file:///opt/plugins/wasm-go/extensions/ip-restriction/plugin.wasm

View File

@@ -0,0 +1,219 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package tests
import (
"testing"
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
func init() {
Register(WasmPluginsIPRestrictionAllow)
Register(WasmPluginsIPRestrictionDeny)
}
var WasmPluginsIPRestrictionAllow = suite.ConformanceTest{
ShortName: "WasmPluginsIPRestrictionAllow",
Description: "The Ingress in the higress-conformance-infra namespace test the ip-restriction wasmplugins.",
Manifests: []string{"tests/go-wasm-ip-restriction-allow.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "10.0.0.1"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "10.0.0.2"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "192.168.5.0"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "192.169.5.0"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
}
t.Run("WasmPlugins ip-restriction", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}
var WasmPluginsIPRestrictionDeny = suite.ConformanceTest{
ShortName: "WasmPluginsIPRestrictionDeny",
Description: "The Ingress in the higress-conformance-infra namespace test the ip-restriction wasmplugins.",
Manifests: []string{"tests/go-wasm-ip-restriction-deny.yaml"},
Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "10.0.0.1"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "10.0.0.2"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "192.168.5.0"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 403,
},
ExpectedResponseNoRequest: true,
},
},
{
Meta: http.AssertionMeta{
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Host: "foo.com",
Path: "/info",
UnfollowRedirect: true,
Headers: map[string]string{"X-REAL-IP": "192.169.5.0"},
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
},
ExpectedResponseNoRequest: true,
},
},
}
t.Run("WasmPlugins ip-restriction", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,277 @@
// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed 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.
package tests
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"sync/atomic"
"github.com/alibaba/higress/test/e2e/conformance/utils/roundtripper"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
"net/url"
"log"
"math/rand"
"net/http"
"sync"
"testing"
"time"
)
func init() {
Register(HttpRouteLimiter)
}
var HttpRouteLimiter = suite.ConformanceTest{
ShortName: "HttpRouteLimiter",
Description: "The Ingress in the higress-conformance-infra namespace uses rps annotation",
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Manifests: []string{"tests/httproute-limit.yaml"},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
t.Run("HTTPRoute limiter", func(t *testing.T) {
//wait ingress ready
time.Sleep(1 * time.Second)
client := &http.Client{}
TestRps10(t, suite.GatewayAddress, client)
TestRps50(t, suite.GatewayAddress, client)
TestRps10Burst3(t, suite.GatewayAddress, client)
TestRpm10(t, suite.GatewayAddress, client)
TestRpm10Burst3(t, suite.GatewayAddress, client)
})
},
}
// TestRps10 test case 1: rps10
func TestRps10(t *testing.T, gwAddr string, client *http.Client) {
req := &roundtripper.Request{
Method: "GET",
Host: "limiter.higress.io",
URL: url.URL{
Scheme: "http",
Host: gwAddr,
Path: "/rps10",
},
}
result, err := ParallelRunner(10, 2000, req, client)
if err != nil {
t.Fatal(err)
}
AssertRps(t, result, 10, 0.5)
}
// TestRps50 test case 2: rps50
func TestRps50(t *testing.T, gwAddr string, client *http.Client) {
req := &roundtripper.Request{
Method: "GET",
Host: "limiter.higress.io",
URL: url.URL{
Scheme: "http",
Host: gwAddr,
Path: "/rps50",
},
}
result, err := ParallelRunner(10, 2000, req, client)
if err != nil {
t.Fatal(err)
}
AssertRps(t, result, 50, 0.5)
}
// TestRps10Burst3 test case 3: rps10 burst3
func TestRps10Burst3(t *testing.T, gwAddr string, client *http.Client) {
req := &roundtripper.Request{
Method: "GET",
Host: "limiter.higress.io",
URL: url.URL{
Scheme: "http",
Host: gwAddr,
Path: "/rps10/burst3",
},
}
result, err := ParallelRunner(30, 50, req, client)
if err != nil {
t.Fatal(err)
}
AssertRps(t, result, 30, -1)
}
// TestRpm10 test case 4: rpm10
func TestRpm10(t *testing.T, gwAddr string, client *http.Client) {
req := &roundtripper.Request{
Method: "GET",
Host: "limiter.higress.io",
URL: url.URL{
Scheme: "http",
Host: gwAddr,
Path: "/rpm10",
},
}
result, err := ParallelRunner(10, 100, req, client)
if err != nil {
t.Fatal(err)
}
AssertRps(t, result, 10, -1)
}
// TestRpm10Burst3 test case 5: rpm10 burst3
func TestRpm10Burst3(t *testing.T, gwAddr string, client *http.Client) {
req := &roundtripper.Request{
Method: "GET",
Host: "limiter.higress.io",
URL: url.URL{
Scheme: "http",
Host: gwAddr,
Path: "/rpm10/burst3",
},
}
result, err := ParallelRunner(30, 100, req, client)
if err != nil {
t.Fatal(err)
}
AssertRps(t, result, 30, -1)
}
// DoRequest send Http request according to req and client, return status code and error
func DoRequest(req *roundtripper.Request, client *http.Client) (int, error) {
u := &url.URL{
Scheme: req.URL.Scheme,
Host: req.URL.Host,
Path: req.URL.Path,
RawQuery: req.URL.RawQuery,
}
r, err := http.NewRequest(req.Method, u.String(), nil)
if err != nil {
return 0, err
}
if r.Host != "" {
r.Host = req.Host
}
if req.Headers != nil {
for name, values := range req.Headers {
for _, value := range values {
r.Header.Add(name, value)
}
}
}
if r.Body != nil {
body, err := json.Marshal(req.Body)
if err != nil {
return 0, err
}
r.Body = io.NopCloser(bytes.NewReader(body))
r.Header.Set("Content-Type", "application/json")
}
resp, err := client.Do(r)
if err != nil {
return 1, err
}
defer client.CloseIdleConnections()
defer resp.Body.Close()
return resp.StatusCode, nil
}
// ParallelRunner send Http request in parallel and count rps
func ParallelRunner(threads int, times int, req *roundtripper.Request, client *http.Client) (*Result, error) {
var wg sync.WaitGroup
result := &Result{
Requests: times,
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
startTime := time.Now()
for i := 0; i < threads; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < times/threads; j++ {
if ctx.Err() != nil {
return
}
b2 := time.Now()
statusCode, err := DoRequest(req, client)
if err != nil {
log.Printf("run() with failed: %v", err)
continue
}
elapsed := time.Since(b2).Nanoseconds() / 1e6
detailRecord := &DetailRecord{
StatusCode: statusCode,
ElapseMs: elapsed,
}
result.DetailMaps.Store(rand.Int(), detailRecord)
if statusCode >= 200 && statusCode < 300 {
atomic.AddInt32(&result.Success, 1)
} else {
time.Sleep(50 * time.Millisecond)
}
}
}()
}
wg.Wait()
result.TotalCostMs = time.Since(startTime).Nanoseconds() / 1e6
result.SuccessRps = float64(result.Success) * 1000 / float64(result.TotalCostMs)
result.ActualRps = float64(result.Requests) * 1000 / float64(result.TotalCostMs)
return result, nil
}
// AssertRps check actual rps is in expected range if tolerance is not -1
// else check actual success requests is less than expected
func AssertRps(t *testing.T, result *Result, expectedRps float64, tolerance float64) {
if tolerance != -1 {
fmt.Printf("Total Cost(s): %.2f, Total Request: %d, Total Success: %d, Actual RPS: %.2f, Expected Rps: %.2f, Success Rps: %.2f\n",
float64(result.TotalCostMs)/1000, result.Requests, result.Success, result.ActualRps, expectedRps, result.SuccessRps)
lo := expectedRps * (1 - tolerance)
hi := expectedRps * (1 + tolerance)
message := fmt.Sprintf("RPS `%.2f` should between `%.2f` - `%.2f`", result.SuccessRps, lo, hi)
if result.SuccessRps < lo || result.SuccessRps > hi {
t.Errorf(message)
}
} else {
fmt.Printf("Total Cost(s): %.2f, Total Request: %d, Total Success: %d, Expected: %.2f\n",
float64(result.TotalCostMs)/1000, result.Requests, result.Success, expectedRps)
message := fmt.Sprintf("Success Requests should less than : %d, actual: %d", int32(expectedRps), result.Success)
if result.Success > int32(expectedRps) {
t.Errorf(message)
}
}
}
type DetailRecord struct {
StatusCode int
ElapseMs int64
}
type Result struct {
Requests int
Success int32
TotalCostMs int64
SuccessRps float64
ActualRps float64
DetailMaps sync.Map
}

View File

@@ -0,0 +1,123 @@
# Copyright (c) 2022 Alibaba Group Holding Ltd.
#
# Licensed 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.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/route-limit-rps: "10"
higress.io/route-limit-burst-multiplier: "1"
name: higress-http-route-limit-rps10
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: limiter.higress.io
http:
paths:
- path: /rps10
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/route-limit-rps: "50"
higress.io/route-limit-burst-multiplier: "1"
name: higress-http-route-limit-rps50
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: limiter.higress.io
http:
paths:
- path: /rps50
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/route-limit-rps: "10"
higress.io/route-limit-burst-multiplier: "3"
name: higress-http-route-limit-rps10-burst3
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: limiter.higress.io
http:
paths:
- path: /rps10/burst3
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/route-limit-rpm: "10"
higress.io/route-limit-burst-multiplier: "1"
name: higress-http-route-limit-rpm10
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: limiter.higress.io
http:
paths:
- path: /rpm10
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/route-limit-rpm: "10"
higress.io/route-limit-burst-multiplier: "3"
name: higress-http-route-limit-rpm10-burst3
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: limiter.higress.io
http:
paths:
- path: /rpm10/burst3
pathType: Exact
backend:
service:
name: infra-backend-v1
port:
number: 8080

View File

@@ -0,0 +1,93 @@
// Copyright (c) 2023 Alibaba Group Holding Ltd.
//
// Licensed 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.
package tests
import (
"testing"
"github.com/alibaba/higress/test/e2e/conformance/utils/http"
"github.com/alibaba/higress/test/e2e/conformance/utils/suite"
)
func init() {
Register(HTTPRouteResponseHeaderControl)
}
var HTTPRouteResponseHeaderControl = suite.ConformanceTest{
ShortName: "HTTPRouteResponseHeaderControl",
Description: "A single Ingress in the higress-conformance-infra namespace controls the response header.",
Manifests: []string{"tests/httproute-response-header-control.yaml"},
Features: []suite.SupportedFeature{suite.HTTPConformanceFeature},
Test: func(t *testing.T, suite *suite.ConformanceTestSuite) {
testcases := []http.Assertion{
{
Meta: http.AssertionMeta{
TestCaseName: "case 1: add one",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
CompareTarget: http.CompareTargetResponse,
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/foo1",
Host: "foo.com",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
Headers: map[string]string{
"stage": "test",
},
},
},
},
{
Meta: http.AssertionMeta{
TestCaseName: "case 2: add more",
TargetBackend: "infra-backend-v1",
TargetNamespace: "higress-conformance-infra",
CompareTarget: http.CompareTargetResponse,
},
Request: http.AssertionRequest{
ActualRequest: http.Request{
Path: "/foo2",
Host: "foo.com",
},
},
Response: http.AssertionResponse{
ExpectedResponse: http.Response{
StatusCode: 200,
Headers: map[string]string{
"stage": "test",
"canary": "true",
"x-test": "higress; test=true",
"x-test2": "higress; test=false",
},
},
},
},
}
t.Run("Response header control", func(t *testing.T) {
for _, testcase := range testcases {
http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase)
}
})
},
}

View File

@@ -0,0 +1,59 @@
# Copyright (c) 2023 Alibaba Group Holding Ltd.
#
# Licensed 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.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/response-header-control-add: stage test
name: httproute-response-header-control-add-one
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Exact
path: "/foo1"
backend:
service:
name: infra-backend-v1
port:
number: 8080
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
higress.io/response-header-control-add: |
stage test
canary true
x-test "higress; test=true"
'x-test2' "higress; test=false"
name: httproute-response-header-control-add-more
namespace: higress-conformance-infra
spec:
ingressClassName: higress
rules:
- host: "foo.com"
http:
paths:
- pathType: Exact
path: "/foo2"
backend:
service:
name: infra-backend-v1
port:
number: 8080

View File

@@ -291,6 +291,16 @@ func (d *DefaultRoundTripper) CaptureRoundTrip(request Request) (*CapturedReques
Path: redirectURL.Path,
}
}
if len(cReq.Namespace) > 0 {
if _, ok := cRes.Headers["Namespace"]; !ok {
cRes.Headers["Namespace"] = []string{cReq.Namespace}
}
}
if len(cReq.Pod) > 0 {
if _, ok := cRes.Headers["Pod"]; !ok {
cRes.Headers["Pod"] = []string{cReq.Pod}
}
}
return cReq, cRes, nil
}