mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
Compare commits
27 Commits
plugins/wa
...
v1.3.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
37fb2a52c0 | ||
|
|
41491166e3 | ||
|
|
29baf8576e | ||
|
|
f1cadcbd73 | ||
|
|
8c817cf80a | ||
|
|
a67ce1d223 | ||
|
|
fb18782a80 | ||
|
|
026840b59b | ||
|
|
75599ef804 | ||
|
|
fe039d46f2 | ||
|
|
6c7b1757b6 | ||
|
|
dfc9ae412e | ||
|
|
b4f72d3584 | ||
|
|
cba2890e14 | ||
|
|
e844daea66 | ||
|
|
717e3bf51f | ||
|
|
ba0df237da | ||
|
|
08e56780f0 | ||
|
|
a45748bb0b | ||
|
|
97cf58e973 | ||
|
|
4d6aa25b19 | ||
|
|
25c2f6e42e | ||
|
|
ed55b65443 | ||
|
|
d64c266ee4 | ||
|
|
32b602704e | ||
|
|
3128df9abd | ||
|
|
cc6043de15 |
2
.github/workflows/build-and-test.yaml
vendored
2
.github/workflows/build-and-test.yaml
vendored
@@ -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
37
.github/workflows/release-hgctl.yaml
vendored
Normal 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
|
||||
@@ -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'
|
||||
|
||||
16
README.md
16
README.md
@@ -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>
|
||||
|
||||
[](https://github.com/alibaba/higress/actions)
|
||||
[](https://github.com/alibaba/higress/actions)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
|
||||
[**官网**](https://higress.io/) |
|
||||
@@ -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 实现了安全防护网关、流量网关、微服务网关三层网关合一,可以显著降低网关的部署和运维成本。
|
||||
|
||||

|
||||
|
||||
@@ -119,9 +119,13 @@ Higress 是基于阿里内部两年多的 Envoy Gateway 实践沉淀,以开源
|
||||
|
||||
如果没有 Envoy 和 Istio 的开源工作,Higress 就不可能实现,在这里向这两个项目献上最诚挚的敬意。
|
||||
|
||||
### 联系我们
|
||||
|
||||
社区交流群:
|
||||
### 交流群
|
||||
|
||||

|
||||
|
||||
### 技术分享
|
||||
|
||||
微信公众号:
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
[](https://github.com/alibaba/higress/actions)
|
||||
[](https://github.com/alibaba/higress/actions)
|
||||
[](https://www.apache.org/licenses/LICENSE-2.0.html)
|
||||
|
||||
[**Official Site**](https://higress.io/en-us/) |
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
20
istio/1.12/patches/istio/20240422-fix-copyvs.patch
Normal file
20
istio/1.12/patches/istio/20240422-fix-copyvs.patch
Normal 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,
|
||||
@@ -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")
|
||||
|
||||
@@ -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{})
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
28
plugins/wasm-go/extensions/cache-control/README.md
Normal file
28
plugins/wasm-go/extensions/cache-control/README.md
Normal 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
|
||||
```
|
||||
|
||||
1
plugins/wasm-go/extensions/cache-control/VERSION
Normal file
1
plugins/wasm-go/extensions/cache-control/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
20
plugins/wasm-go/extensions/cache-control/go.mod
Normal file
20
plugins/wasm-go/extensions/cache-control/go.mod
Normal 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
|
||||
)
|
||||
86
plugins/wasm-go/extensions/cache-control/main.go
Normal file
86
plugins/wasm-go/extensions/cache-control/main.go
Normal 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
|
||||
}
|
||||
31
plugins/wasm-go/extensions/ip-restriction/README.md
Normal file
31
plugins/wasm-go/extensions/ip-restriction/README.md
Normal 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
|
||||
```
|
||||
1
plugins/wasm-go/extensions/ip-restriction/VERSION
Normal file
1
plugins/wasm-go/extensions/ip-restriction/VERSION
Normal file
@@ -0,0 +1 @@
|
||||
1.0.0
|
||||
22
plugins/wasm-go/extensions/ip-restriction/go.mod
Normal file
22
plugins/wasm-go/extensions/ip-restriction/go.mod
Normal 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
|
||||
)
|
||||
24
plugins/wasm-go/extensions/ip-restriction/go.sum
Normal file
24
plugins/wasm-go/extensions/ip-restriction/go.sum
Normal 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=
|
||||
157
plugins/wasm-go/extensions/ip-restriction/main.go
Normal file
157
plugins/wasm-go/extensions/ip-restriction/main.go
Normal 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
|
||||
}
|
||||
37
plugins/wasm-go/extensions/ip-restriction/utils.go
Normal file
37
plugins/wasm-go/extensions/ip-restriction/utils.go
Normal 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
|
||||
}
|
||||
106
plugins/wasm-go/extensions/ip-restriction/utils_test.go
Normal file
106
plugins/wasm-go/extensions/ip-restriction/utils_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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转换的时候只支持最多一个映射来源
|
||||
// 取值:headers,querys,body,self
|
||||
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 {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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{
|
||||
|
||||
63
test/e2e/conformance/tests/go-wasm-cache-control.go
Normal file
63
test/e2e/conformance/tests/go-wasm-cache-control.go
Normal 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)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
48
test/e2e/conformance/tests/go-wasm-cache-control.yaml
Normal file
48
test/e2e/conformance/tests/go-wasm-cache-control.yaml
Normal 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
|
||||
46
test/e2e/conformance/tests/go-wasm-ip-restriction-allow.yaml
Normal file
46
test/e2e/conformance/tests/go-wasm-ip-restriction-allow.yaml
Normal 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
|
||||
46
test/e2e/conformance/tests/go-wasm-ip-restriction-deny.yaml
Normal file
46
test/e2e/conformance/tests/go-wasm-ip-restriction-deny.yaml
Normal 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
|
||||
219
test/e2e/conformance/tests/go-wasm-ip-restriction.go
Normal file
219
test/e2e/conformance/tests/go-wasm-ip-restriction.go
Normal 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)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
277
test/e2e/conformance/tests/httproute-limit.go
Normal file
277
test/e2e/conformance/tests/httproute-limit.go
Normal 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
|
||||
}
|
||||
123
test/e2e/conformance/tests/httproute-limit.yaml
Normal file
123
test/e2e/conformance/tests/httproute-limit.yaml
Normal 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
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user