mirror of
https://github.com/alibaba/higress.git
synced 2026-02-25 21:21:01 +08:00
Compare commits
17 Commits
v2.0.6-rc.
...
v2.0.6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd1eb54f25 | ||
|
|
c7550e2d49 | ||
|
|
ba74f4bbb9 | ||
|
|
9e418dafd9 | ||
|
|
95523a1bc7 | ||
|
|
dcd8466127 | ||
|
|
cceae6ad2a | ||
|
|
32f9a5ff32 | ||
|
|
6f95297b80 | ||
|
|
95426d5ccf | ||
|
|
a05b6b1e9d | ||
|
|
d0628344da | ||
|
|
a1bf315b13 | ||
|
|
b3d9123d59 | ||
|
|
817061c6cc | ||
|
|
ea0d5e7564 | ||
|
|
2a89c3bb70 |
4
.github/workflows/build-and-test-plugin.yaml
vendored
4
.github/workflows/build-and-test-plugin.yaml
vendored
@@ -6,11 +6,15 @@ on:
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'test/**'
|
||||
- 'helm/**'
|
||||
- 'Makefile.core.mk'
|
||||
pull_request:
|
||||
branches: [ "*" ]
|
||||
paths:
|
||||
- 'plugins/**'
|
||||
- 'test/**'
|
||||
- 'helm/**'
|
||||
- 'Makefile.core.mk'
|
||||
workflow_dispatch: ~
|
||||
|
||||
jobs:
|
||||
|
||||
2
.github/workflows/deploy-to-oss.yaml
vendored
2
.github/workflows/deploy-to-oss.yaml
vendored
@@ -19,7 +19,7 @@ jobs:
|
||||
- name: Download Helm Charts Index
|
||||
uses: doggycool/ossutil-github-action@master
|
||||
with:
|
||||
ossArgs: 'cp -r -u oss://higress-website-cn-hongkong/helm-charts/index.yaml ./artifact/'
|
||||
ossArgs: 'cp oss://higress-website-cn-hongkong/helm-charts/index.yaml ./artifact/'
|
||||
accessKey: ${{ secrets.ACCESS_KEYID }}
|
||||
accessSecret: ${{ secrets.ACCESS_KEYSECRET }}
|
||||
endpoint: oss-cn-hongkong.aliyuncs.com
|
||||
|
||||
@@ -12,6 +12,7 @@ header:
|
||||
- 'LICENSE'
|
||||
- 'api/**'
|
||||
- 'samples/**'
|
||||
- 'docs/**'
|
||||
- '.github/**'
|
||||
- '.licenserc.yaml'
|
||||
- 'helm/**'
|
||||
|
||||
@@ -188,7 +188,7 @@ install: pre-install
|
||||
helm install higress helm/higress -n higress-system --create-namespace --set 'global.local=true'
|
||||
|
||||
ENVOY_LATEST_IMAGE_TAG ?= 958467a353d411ae3f06e03b096bfd342cddb2c6
|
||||
ISTIO_LATEST_IMAGE_TAG ?= 958467a353d411ae3f06e03b096bfd342cddb2c6
|
||||
ISTIO_LATEST_IMAGE_TAG ?= d9c728d3b01f64855e012b08d136e306f1160397
|
||||
|
||||
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'
|
||||
|
||||
@@ -341,7 +341,7 @@ type WasmPlugin struct {
|
||||
// Extended by Higress, matching rules take effect
|
||||
MatchRules []*MatchRule `protobuf:"bytes,102,rep,name=match_rules,json=matchRules,proto3" json:"match_rules,omitempty"`
|
||||
// disable the default config
|
||||
DefaultConfigDisable bool `protobuf:"varint,103,opt,name=default_config_disable,json=defaultConfigDisable,proto3" json:"default_config_disable,omitempty"`
|
||||
DefaultConfigDisable *wrappers.BoolValue `protobuf:"bytes,103,opt,name=default_config_disable,json=defaultConfigDisable,proto3" json:"default_config_disable,omitempty"`
|
||||
}
|
||||
|
||||
func (x *WasmPlugin) Reset() {
|
||||
@@ -467,11 +467,11 @@ func (x *WasmPlugin) GetMatchRules() []*MatchRule {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *WasmPlugin) GetDefaultConfigDisable() bool {
|
||||
func (x *WasmPlugin) GetDefaultConfigDisable() *wrappers.BoolValue {
|
||||
if x != nil {
|
||||
return x.DefaultConfigDisable
|
||||
}
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
// Extended by Higress
|
||||
@@ -480,11 +480,11 @@ type MatchRule struct {
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Ingress []string `protobuf:"bytes,1,rep,name=ingress,proto3" json:"ingress,omitempty"`
|
||||
Domain []string `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
Config *_struct.Struct `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"`
|
||||
ConfigDisable bool `protobuf:"varint,4,opt,name=config_disable,json=configDisable,proto3" json:"config_disable,omitempty"`
|
||||
Service []string `protobuf:"bytes,5,rep,name=service,proto3" json:"service,omitempty"`
|
||||
Ingress []string `protobuf:"bytes,1,rep,name=ingress,proto3" json:"ingress,omitempty"`
|
||||
Domain []string `protobuf:"bytes,2,rep,name=domain,proto3" json:"domain,omitempty"`
|
||||
Config *_struct.Struct `protobuf:"bytes,3,opt,name=config,proto3" json:"config,omitempty"`
|
||||
ConfigDisable *wrappers.BoolValue `protobuf:"bytes,4,opt,name=config_disable,json=configDisable,proto3" json:"config_disable,omitempty"`
|
||||
Service []string `protobuf:"bytes,5,rep,name=service,proto3" json:"service,omitempty"`
|
||||
}
|
||||
|
||||
func (x *MatchRule) Reset() {
|
||||
@@ -540,11 +540,11 @@ func (x *MatchRule) GetConfig() *_struct.Struct {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MatchRule) GetConfigDisable() bool {
|
||||
func (x *MatchRule) GetConfigDisable() *wrappers.BoolValue {
|
||||
if x != nil {
|
||||
return x.ConfigDisable
|
||||
}
|
||||
return false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *MatchRule) GetService() []string {
|
||||
@@ -686,7 +686,7 @@ var file_extensions_v1alpha1_wasmplugin_proto_rawDesc = []byte{
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x77, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x73, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x62, 0x75, 0x66, 0x2f, 0x73, 0x74, 0x72, 0x75, 0x63, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x22, 0x8d, 0x06, 0x0a, 0x0a, 0x57, 0x61, 0x73, 0x6d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x6f, 0x22, 0xa9, 0x06, 0x0a, 0x0a, 0x57, 0x61, 0x73, 0x6d, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75,
|
||||
0x72, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x18, 0x03, 0x20, 0x01,
|
||||
0x28, 0x09, 0x52, 0x06, 0x73, 0x68, 0x61, 0x32, 0x35, 0x36, 0x12, 0x53, 0x0a, 0x11, 0x69, 0x6d,
|
||||
@@ -731,52 +731,55 @@ var file_extensions_v1alpha1_wasmplugin_proto_rawDesc = []byte{
|
||||
0x73, 0x18, 0x66, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73,
|
||||
0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61,
|
||||
0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x52,
|
||||
0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x64,
|
||||
0x0a, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x50, 0x0a, 0x16, 0x64,
|
||||
0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69,
|
||||
0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x67, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x64, 0x65, 0x66,
|
||||
0x61, 0x75, 0x6c, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c,
|
||||
0x65, 0x22, 0xaf, 0x01, 0x0a, 0x09, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09,
|
||||
0x52, 0x07, 0x69, 0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d,
|
||||
0x61, 0x69, 0x6e, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69,
|
||||
0x6e, 0x12, 0x2f, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28,
|
||||
0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f,
|
||||
0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x12, 0x25, 0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x73,
|
||||
0x61, 0x62, 0x6c, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66,
|
||||
0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72,
|
||||
0x76, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76,
|
||||
0x69, 0x63, 0x65, 0x22, 0x41, 0x0a, 0x08, 0x56, 0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12,
|
||||
0x35, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68,
|
||||
0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61,
|
||||
0x72, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x22, 0x7e, 0x0a, 0x06, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72,
|
||||
0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04,
|
||||
0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x0a, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x72,
|
||||
0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65,
|
||||
0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31,
|
||||
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53,
|
||||
0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x72, 0x6f, 0x6d,
|
||||
0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||
0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x45, 0x0a, 0x0b, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e,
|
||||
0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x15, 0x0a, 0x11, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
|
||||
0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x48, 0x41, 0x53, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05,
|
||||
0x41, 0x55, 0x54, 0x48, 0x4e, 0x10, 0x01, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x5a,
|
||||
0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54, 0x41, 0x54, 0x53, 0x10, 0x03, 0x2a, 0x42, 0x0a,
|
||||
0x0a, 0x50, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x55,
|
||||
0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43,
|
||||
0x59, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49, 0x66, 0x4e, 0x6f, 0x74, 0x50, 0x72, 0x65, 0x73,
|
||||
0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10,
|
||||
0x02, 0x2a, 0x26, 0x0a, 0x0e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75,
|
||||
0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x49, 0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12,
|
||||
0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, 0x10, 0x01, 0x2a, 0x2d, 0x0a, 0x0c, 0x46, 0x61, 0x69,
|
||||
0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67, 0x79, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x41, 0x49,
|
||||
0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68,
|
||||
0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68,
|
||||
0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x73, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x67, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f,
|
||||
0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f,
|
||||
0x6f, 0x6c, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x52, 0x14, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74,
|
||||
0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x22, 0xcb, 0x01,
|
||||
0x0a, 0x09, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x69,
|
||||
0x6e, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x69, 0x6e,
|
||||
0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18,
|
||||
0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x64, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x2f, 0x0a,
|
||||
0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e,
|
||||
0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e,
|
||||
0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41,
|
||||
0x0a, 0x0e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65,
|
||||
0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e,
|
||||
0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x42, 0x6f, 0x6f, 0x6c, 0x56, 0x61, 0x6c,
|
||||
0x75, 0x65, 0x52, 0x0d, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c,
|
||||
0x65, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x05, 0x20, 0x03,
|
||||
0x28, 0x09, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0x41, 0x0a, 0x08, 0x56,
|
||||
0x6d, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x35, 0x0a, 0x03, 0x65, 0x6e, 0x76, 0x18, 0x01,
|
||||
0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65,
|
||||
0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68,
|
||||
0x61, 0x31, 0x2e, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x52, 0x03, 0x65, 0x6e, 0x76, 0x22, 0x7e,
|
||||
0x0a, 0x06, 0x45, 0x6e, 0x76, 0x56, 0x61, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65,
|
||||
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x4a, 0x0a, 0x0a,
|
||||
0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e,
|
||||
0x32, 0x2b, 0x2e, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e,
|
||||
0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x2e, 0x45,
|
||||
0x6e, 0x76, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x09, 0x76,
|
||||
0x61, 0x6c, 0x75, 0x65, 0x46, 0x72, 0x6f, 0x6d, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75,
|
||||
0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x2a, 0x45,
|
||||
0x0a, 0x0b, 0x50, 0x6c, 0x75, 0x67, 0x69, 0x6e, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x15, 0x0a,
|
||||
0x11, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x50, 0x48, 0x41,
|
||||
0x53, 0x45, 0x10, 0x00, 0x12, 0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x4e, 0x10, 0x01, 0x12,
|
||||
0x09, 0x0a, 0x05, 0x41, 0x55, 0x54, 0x48, 0x5a, 0x10, 0x02, 0x12, 0x09, 0x0a, 0x05, 0x53, 0x54,
|
||||
0x41, 0x54, 0x53, 0x10, 0x03, 0x2a, 0x42, 0x0a, 0x0a, 0x50, 0x75, 0x6c, 0x6c, 0x50, 0x6f, 0x6c,
|
||||
0x69, 0x63, 0x79, 0x12, 0x16, 0x0a, 0x12, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49,
|
||||
0x45, 0x44, 0x5f, 0x50, 0x4f, 0x4c, 0x49, 0x43, 0x59, 0x10, 0x00, 0x12, 0x10, 0x0a, 0x0c, 0x49,
|
||||
0x66, 0x4e, 0x6f, 0x74, 0x50, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x74, 0x10, 0x01, 0x12, 0x0a, 0x0a,
|
||||
0x06, 0x41, 0x6c, 0x77, 0x61, 0x79, 0x73, 0x10, 0x02, 0x2a, 0x26, 0x0a, 0x0e, 0x45, 0x6e, 0x76,
|
||||
0x56, 0x61, 0x6c, 0x75, 0x65, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x0a, 0x0a, 0x06, 0x49,
|
||||
0x4e, 0x4c, 0x49, 0x4e, 0x45, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x48, 0x4f, 0x53, 0x54, 0x10,
|
||||
0x01, 0x2a, 0x2d, 0x0a, 0x0c, 0x46, 0x61, 0x69, 0x6c, 0x53, 0x74, 0x72, 0x61, 0x74, 0x65, 0x67,
|
||||
0x79, 0x12, 0x0e, 0x0a, 0x0a, 0x46, 0x41, 0x49, 0x4c, 0x5f, 0x43, 0x4c, 0x4f, 0x53, 0x45, 0x10,
|
||||
0x00, 0x12, 0x0d, 0x0a, 0x09, 0x46, 0x41, 0x49, 0x4c, 0x5f, 0x4f, 0x50, 0x45, 0x4e, 0x10, 0x01,
|
||||
0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61,
|
||||
0x6c, 0x69, 0x62, 0x61, 0x62, 0x61, 0x2f, 0x68, 0x69, 0x67, 0x72, 0x65, 0x73, 0x73, 0x2f, 0x61,
|
||||
0x70, 0x69, 0x2f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31,
|
||||
0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -804,6 +807,7 @@ var file_extensions_v1alpha1_wasmplugin_proto_goTypes = []interface{}{
|
||||
(*EnvVar)(nil), // 7: higress.extensions.v1alpha1.EnvVar
|
||||
(*_struct.Struct)(nil), // 8: google.protobuf.Struct
|
||||
(*wrappers.Int32Value)(nil), // 9: google.protobuf.Int32Value
|
||||
(*wrappers.BoolValue)(nil), // 10: google.protobuf.BoolValue
|
||||
}
|
||||
var file_extensions_v1alpha1_wasmplugin_proto_depIdxs = []int32{
|
||||
1, // 0: higress.extensions.v1alpha1.WasmPlugin.image_pull_policy:type_name -> higress.extensions.v1alpha1.PullPolicy
|
||||
@@ -814,14 +818,16 @@ var file_extensions_v1alpha1_wasmplugin_proto_depIdxs = []int32{
|
||||
6, // 5: higress.extensions.v1alpha1.WasmPlugin.vm_config:type_name -> higress.extensions.v1alpha1.VmConfig
|
||||
8, // 6: higress.extensions.v1alpha1.WasmPlugin.default_config:type_name -> google.protobuf.Struct
|
||||
5, // 7: higress.extensions.v1alpha1.WasmPlugin.match_rules:type_name -> higress.extensions.v1alpha1.MatchRule
|
||||
8, // 8: higress.extensions.v1alpha1.MatchRule.config:type_name -> google.protobuf.Struct
|
||||
7, // 9: higress.extensions.v1alpha1.VmConfig.env:type_name -> higress.extensions.v1alpha1.EnvVar
|
||||
2, // 10: higress.extensions.v1alpha1.EnvVar.value_from:type_name -> higress.extensions.v1alpha1.EnvValueSource
|
||||
11, // [11:11] is the sub-list for method output_type
|
||||
11, // [11:11] is the sub-list for method input_type
|
||||
11, // [11:11] is the sub-list for extension type_name
|
||||
11, // [11:11] is the sub-list for extension extendee
|
||||
0, // [0:11] is the sub-list for field type_name
|
||||
10, // 8: higress.extensions.v1alpha1.WasmPlugin.default_config_disable:type_name -> google.protobuf.BoolValue
|
||||
8, // 9: higress.extensions.v1alpha1.MatchRule.config:type_name -> google.protobuf.Struct
|
||||
10, // 10: higress.extensions.v1alpha1.MatchRule.config_disable:type_name -> google.protobuf.BoolValue
|
||||
7, // 11: higress.extensions.v1alpha1.VmConfig.env:type_name -> higress.extensions.v1alpha1.EnvVar
|
||||
2, // 12: higress.extensions.v1alpha1.EnvVar.value_from:type_name -> higress.extensions.v1alpha1.EnvValueSource
|
||||
13, // [13:13] is the sub-list for method output_type
|
||||
13, // [13:13] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_extensions_v1alpha1_wasmplugin_proto_init() }
|
||||
|
||||
@@ -112,7 +112,7 @@ message WasmPlugin {
|
||||
// Extended by Higress, matching rules take effect
|
||||
repeated MatchRule match_rules = 102;
|
||||
// disable the default config
|
||||
bool default_config_disable = 103;
|
||||
google.protobuf.BoolValue default_config_disable = 103;
|
||||
}
|
||||
|
||||
// Extended by Higress
|
||||
@@ -120,7 +120,7 @@ message MatchRule {
|
||||
repeated string ingress = 1;
|
||||
repeated string domain = 2;
|
||||
google.protobuf.Struct config = 3;
|
||||
bool config_disable = 4;
|
||||
google.protobuf.BoolValue config_disable = 4;
|
||||
repeated string service = 5;
|
||||
}
|
||||
|
||||
|
||||
143
docs/architecture.md
Normal file
143
docs/architecture.md
Normal file
@@ -0,0 +1,143 @@
|
||||
# Higress 核心组件和原理
|
||||
|
||||
Higress 是基于 Envoy 和 Istio 进行二次定制化开发构建和功能增强,同时利用 Envoy 和 Istio 一些插件机制,实现了一个轻量级的网关服务。其包括 3 个核心组件:Higress Controller(控制器)、Higress Gateway(网关)和 Higress Console(控制台)。
|
||||
下图概况了其核心工作流程:
|
||||
|
||||

|
||||
|
||||
本章将重点介绍 Higress 的两个核心组件:Higress Controller 和 Higress Gateway。
|
||||
|
||||
## 1 Higress Console
|
||||
|
||||
Higress Console 是 Higress 网关的管理控制台,主要功能是管理 Higress 网关的路由配置、插件配置等。
|
||||
|
||||
### 1.1 Higress Admin SDK
|
||||
|
||||
Higress Admin SDK 脱胎于 Higress Console。起初,它作为 Higress Console 的一部分,为前端界面提供实际的功能支持。后来考虑到对接外部系统等需求,将配置管理的部分剥离出来,形成一个独立的逻辑组件,便于和各个系统进行对接。目前支持服务来源管理、服务管理、路由管理、域名管理、证书管理、插件管理等功能。
|
||||
Higress Admin SDK 现在只提供 Java 版本,且要求 JDK 版本不低于 17。具体如何集成请参考 Higress 官方 BLOG [如何使用 Higress Admin SDK 进行配置管理](https://higress.io/zh-cn/blog/admin-sdk-intro)。
|
||||
|
||||
## 2 Higress Controller
|
||||
|
||||
Higress Controller(控制器) 是 Higress 的核心组件,其功能主要是实现 Higress 网关的服务发现、动态配置管理,以及动态下发配置给数据面。Higress Controller 内部包含两个子组件:Discovery 和 Higress Core。
|
||||
|
||||
### 2.1 Discovery 组件
|
||||
|
||||
Discovery 组件(Istio Pilot-Discovery)是 Istio 的核心组件,负责服务发现、配置管理、证书签发、控制面和数据面之间的通讯和配置下发等。Discovery 内部结构比较复杂,本文只介绍 Discovery 配置管理和服务发现的基本原理,其核心功能的详细介绍可以参考赵化冰老师的 BLOG [Istio Pilot 组件介绍](https://www.zhaohuabing.com/post/2019-10-21-pilot-discovery-code-analysis/)。
|
||||
Discovery 将 Kubernetes Service、Gateway API 配置等转换成 Istio 配置,然后将所有 Istio 配置合并转成符合 xDS 接口规范的数据结构,通过 GRPC 下发到数据面的 Envoy。其工作原理如下图:
|
||||
|
||||

|
||||
|
||||
#### 2.1.1 Config Controller
|
||||
|
||||
Discovery 为了更好管理 Istio 配置来源,提供 `Config Controller` 用于管理各种配置来源,目前支持 4 种类型的 `Config Controller`:
|
||||
|
||||
- Kubernetes:使用 Kubernetes 作为配置信息来源,该方式的直接依赖 Kubernetes 强大的 CRD 机制来存储配置信息,简单方便,是 Istio 最开始使用的配置信息存储方案, 其中包括 `Kubernetes Controller` 和 `Gateway API Controller` 两个实现。
|
||||
- MCP(Mesh Configuration Protocol):使用 Kubernetes 存储配置数据导致了 Istio 和 Kubernetes 的耦合,限制了 Istio 在非 Kubernetes 环境下的运用。为了解决该耦合,Istio 社区提出了 MCP。
|
||||
- Memory:一个基于内存的 Config Controller 实现,主要用于测试。
|
||||
- File:一个基于文件的 Config Controller 实现,主要用于测试。
|
||||
|
||||
1. Istio 配置
|
||||
|
||||
Istio 配置包括:`Gateway`、`VirtualService`、`DestinationRule`、`ServiceEntry`、`EnvoyFilter`、`WasmPlugin`、`WorkloadEntry`、`WorkloadGroup` 等,可以参考 Istio 官方文档[流量管理](https://istio.io/latest/zh/docs/reference/config/networking/)了解更多配置信息。
|
||||
|
||||
2. Gateway API 配置
|
||||
|
||||
Gateway API 配置包括:`GatewayClass`、`Gateway`、`HttpRoute`、`TCPRoute`、`GRPCRoute` 等, 可以参考 Gateway API 官方文档 [Gateway API](https://gateway-api.sigs.k8s.io/api-types/gateway/) 了解更多配置信息。
|
||||
|
||||
3. MCP over xDS
|
||||
|
||||
Discovery 作为 MCP Client,任何实现了 MCP 协议的 Server 都可以通过 MCP 协议向 Discovery 下发配置信息,从而消除了 Istio 和 Kubernetes 之间的耦合, 同时使 Istio 的配置信息处理更加灵活和可扩展。
|
||||
同时 MCP 是一种基于 xDS 协议的配置管理协议,Higress Core 通过实现 MCP 协议,使 Higress Core 成为 Discovery 的 Istio 配置来源。
|
||||
|
||||
4. Config Controller 来源配置
|
||||
|
||||
在 `higress-system` 命名空间中,名为 `higress-config` 的 Configmap 中,`mesh` 配置项包含一个 `configSources` 属性用于配置来源。其 Configmap 部分配置项如下:
|
||||
|
||||
```yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: higress-config
|
||||
namespace: higress-system
|
||||
data:
|
||||
mesh: |-
|
||||
accessLogEncoding: TEXT
|
||||
...
|
||||
configSources:
|
||||
- address: xds://127.0.0.1:15051
|
||||
- address: k8s://
|
||||
...
|
||||
meshNetworks: "networks: {}"
|
||||
```
|
||||
|
||||
#### 2.1.2 Service Controller
|
||||
|
||||
`Service Controller` 用于管理各种 `Service Registry`,提供服务发现数据,目前 Istio 支持的 `Service Registry` 主要包括:
|
||||
|
||||
- Kubernetes:对接 Kubernetes Registry,可以将 Kubernetes 中定义的 Service 和 Endpoint 采集到 Istio 中。
|
||||
- Memory:一个基于内存的 Service Controller 实现,主要用于测试。
|
||||
|
||||
### 2.2 Higress Core 组件
|
||||
|
||||
Higress Core 核心逻辑如下图:
|
||||
|
||||

|
||||
|
||||
|
||||
Higress Core 内部包含两个核心子组件: Ingress Config 和 Cert Server。
|
||||
|
||||
#### 2.2.1 Ingress Config
|
||||
|
||||
Ingress Config 包含 6 个控制器,各自负责不同的功能:
|
||||
|
||||
- Ingress Controller:监听 Ingress 资源,将 Ingress 转换为 Istio 的 Gateway、VirtualService、DestinationRule 等资源。
|
||||
- Gateway Controller:监听 Gateway、VirtualService、DestinationRule 等资源。
|
||||
- McpBridge Controller:根据 McpBridge 的配置,将来自 Nacos、Eureka、Consul、Zookeeper 等外部注册中心或 DNS 的服务信息转换成 Istio ServiceEntry 资源。
|
||||
- Http2Rpc Controller:监听 Http2Rpc 资源,实现 HTTP 协议到 RPC 协议的转换。用户可以通过配置协议转换,将 RPC 服务以 HTTP 接口的形式暴露,从而使用 HTTP 请求调用 RPC 接口。
|
||||
- WasmPlugin Controller:监听 WasmPlugin 资源,将 Higress WasmPlugin 转化为 Istio WasmPlugin。Higress WasmPlugin 在 Istio WasmPlugin 的基础上进行了扩展,支持全局、路由、域名、服务级别的配置。
|
||||
- ConfigmapMgr:监听 Higress 的全局配置 `higress-config` ConfigMap,可以根据 tracing、gzip 等配置构造 EnvoyFilter。
|
||||
|
||||
#### 2.2.2 Cert Server
|
||||
|
||||
Cert Server 管理 Secret 资源和证书自动签发。
|
||||
|
||||
## 3 Higress Gateway
|
||||
|
||||
Higress Gateway 内部包含两个子组件:Pilot Agent 和 Envoy。Pilot Agent 主要负责 Envoy 的启动和配置,同时代理 Envoy xDS 请求到 Discovery。 Envoy 作为数据面,负责接收控制面的配置下发,并代理请求到业务服务。 Pilot Agent 和 Envoy 之间通讯协议是使用 xDS 协议, 通过 Unix Domain Socket(UDS)进行通信。
|
||||
Envoy 核心架构如下图:
|
||||
|
||||

|
||||
|
||||
### 1 Envoy 核心组件
|
||||
|
||||
- 下游(Downstream):
|
||||
下游是 Envoy 的客户端,它们负责发起请求并接收 Envoy 的响应。下游通常是最终用户的设备或服务,它们通过 Envoy 代理与后端服务进行通信。
|
||||
|
||||
- 上游(Upstream):
|
||||
上游是 Envoy 的后端服务器,它们接收 Envoy 代理的连接和请求。上游提供服务或数据,对来自下游客户端的请求进行处理并返回响应。
|
||||
|
||||
- 监听器(Listener):
|
||||
监听器是可以接受来自下游客户端连接的网络地址(如 IP 地址和端口,Unix Domain Socket 等)。Envoy 支持在单个进程中配置任意数量的监听器。监听器可以通过 `Listener Discovery Service(LDS)`来动态发现和更新。
|
||||
|
||||
- 路由(Router):
|
||||
路由器是 Envoy 中连接下游和上游的桥梁。它负责决定如何将监听器接收到的请求路由到适当的集群。路由器根据配置的路由规则,如路径、HTTP 标头 等,来确定请求的目标集群,从而实现精确的流量控制和路由。路由器可以通过 `Route Discovery Service(RDS)`来动态发现和更新。
|
||||
|
||||
- 集群(Cluster):
|
||||
集群是一组逻辑上相似的服务提供者的集合。集群成员的选择由负载均衡策略决定,确保请求能够均匀或按需分配到不同的服务实例。集群可以通过 `Cluster Discovery Service(CDS)`来动态发现和更新。
|
||||
|
||||
- 端点(Endpoint):
|
||||
端点是上游集群中的具体服务实例,可以是 IP 地址和端口号的组合。端点可以通过 `Endpoint Discovery Service(EDS)`来动态发现和更新。
|
||||
|
||||
- SSL/TLS:
|
||||
Envoy 可以通过 `Secret Discovery Service (SDS)` 动态获取监听器和集群所需的 TLS 证书、私钥以及信任的根证书和撤销机制等配置信息。
|
||||
|
||||
通过这些组件的协同工作,Envoy 能够高效地处理网络请求,提供流量管理、负载均衡、服务发现和动态路由等关键功能。
|
||||
要详细了解 Envoy 的工作原理,可以参考[Envoy 官方文档](https://www.envoyproxy.io/docs/envoy/latest/intro/intro),最佳的方式可以通过一个请求通过 [Envoy 代理的生命周期](https://www.envoyproxy.io/docs/envoy/latest/intro/life_of_a_request)事件的过程来理解 Envoy 的工作原理。
|
||||
|
||||
|
||||
## 参考
|
||||
|
||||
- [1] [Istio Pilot 组件介绍](https://www.zhaohuabing.com/post/2019-10-21-pilot-discovery-code-analysis/)
|
||||
- [2] [Istio 服务注册插件机制代码解析](https://www.zhaohuabing.com/post/2019-02-18-pilot-service-registry-code-analysis/)
|
||||
- [3] [Istio Pilot代码深度解析](https://www.zhaohuabing.com/post/2019-10-21-pilot-discovery-code-analysis/)
|
||||
- [4] [Envoy 官方文档](https://www.envoyproxy.io/docs/envoy/latest/intro/intro)
|
||||
BIN
docs/images/img_02_01.png
Normal file
BIN
docs/images/img_02_01.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 119 KiB |
BIN
docs/images/img_02_02.png
Normal file
BIN
docs/images/img_02_02.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
BIN
docs/images/img_02_03.png
Normal file
BIN
docs/images/img_02_03.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 167 KiB |
BIN
docs/images/img_02_04.png
Normal file
BIN
docs/images/img_02_04.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 127 KiB |
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 2.0.6-rc.1
|
||||
appVersion: 2.0.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: 2.0.6-rc.1
|
||||
version: 2.0.6
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
accessLogFile: "/dev/stdout"
|
||||
{{- end }}
|
||||
ingressControllerMode: "OFF"
|
||||
accessLogFormat: '{"authority":"%REQ(X-ENVOY-ORIGINAL-HOST?:AUTHORITY)%","bytes_received":"%BYTES_RECEIVED%","bytes_sent":"%BYTES_SENT%","downstream_local_address":"%DOWNSTREAM_LOCAL_ADDRESS%","downstream_remote_address":"%DOWNSTREAM_REMOTE_ADDRESS%","duration":"%DURATION%","istio_policy_status":"%DYNAMIC_METADATA(istio.mixer:status)%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","request_id":"%REQ(X-REQUEST-ID)%","requested_server_name":"%REQUESTED_SERVER_NAME%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","route_name":"%ROUTE_NAME%","start_time":"%START_TIME%","trace_id":"%REQ(X-B3-TRACEID)%","upstream_cluster":"%UPSTREAM_CLUSTER%","upstream_host":"%UPSTREAM_HOST%","upstream_local_address":"%UPSTREAM_LOCAL_ADDRESS%","upstream_service_time":"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%","upstream_transport_failure_reason":"%UPSTREAM_TRANSPORT_FAILURE_REASON%","user_agent":"%REQ(USER-AGENT)%","x_forwarded_for":"%REQ(X-FORWARDED-FOR)%","response_code_details":"%RESPONSE_CODE_DETAILS%"}
|
||||
accessLogFormat: '{"ai_log":"%FILTER_STATE(wasm.ai_log:PLAIN)%","authority":"%REQ(X-ENVOY-ORIGINAL-HOST?:AUTHORITY)%","bytes_received":"%BYTES_RECEIVED%","bytes_sent":"%BYTES_SENT%","downstream_local_address":"%DOWNSTREAM_LOCAL_ADDRESS%","downstream_remote_address":"%DOWNSTREAM_REMOTE_ADDRESS%","duration":"%DURATION%","istio_policy_status":"%DYNAMIC_METADATA(istio.mixer:status)%","method":"%REQ(:METHOD)%","path":"%REQ(X-ENVOY-ORIGINAL-PATH?:PATH)%","protocol":"%PROTOCOL%","request_id":"%REQ(X-REQUEST-ID)%","requested_server_name":"%REQUESTED_SERVER_NAME%","response_code":"%RESPONSE_CODE%","response_flags":"%RESPONSE_FLAGS%","route_name":"%ROUTE_NAME%","start_time":"%START_TIME%","trace_id":"%REQ(X-B3-TRACEID)%","upstream_cluster":"%UPSTREAM_CLUSTER%","upstream_host":"%UPSTREAM_HOST%","upstream_local_address":"%UPSTREAM_LOCAL_ADDRESS%","upstream_service_time":"%RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)%","upstream_transport_failure_reason":"%UPSTREAM_TRANSPORT_FAILURE_REASON%","user_agent":"%REQ(USER-AGENT)%","x_forwarded_for":"%REQ(X-FORWARDED-FOR)%","response_code_details":"%RESPONSE_CODE_DETAILS%"}
|
||||
|
||||
'
|
||||
dnsRefreshRate: 200s
|
||||
|
||||
@@ -136,8 +136,10 @@ spec:
|
||||
periodSeconds: 3
|
||||
timeoutSeconds: 5
|
||||
env:
|
||||
- name: ENABLE_PUSH_ALL_MCP_CLUSTERS
|
||||
value: "{{ .Values.global.enablePushAllMCPClusters }}"
|
||||
- name: PILOT_ENABLE_LDS_CACHE
|
||||
valvue: "{{ .Values.global.enableLDSCache }}"
|
||||
value: "{{ .Values.global.enableLDSCache }}"
|
||||
- name: PILOT_ENABLE_QUIC_LISTENERS
|
||||
value: "true"
|
||||
- name: VALIDATION_WEBHOOK_CONFIG_NAME
|
||||
|
||||
@@ -4,6 +4,7 @@ global:
|
||||
enableIPv6: false
|
||||
enableProxyProtocol: false
|
||||
enableLDSCache: true
|
||||
enablePushAllMCPClusters: true
|
||||
liteMetrics: false
|
||||
xdsMaxRecvMsgSize: "104857600"
|
||||
defaultUpstreamConcurrencyThreshold: 10000
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
dependencies:
|
||||
- name: higress-core
|
||||
repository: file://../core
|
||||
version: 2.0.6-rc.1
|
||||
version: 2.0.6
|
||||
- name: higress-console
|
||||
repository: https://higress.io/helm-charts/
|
||||
version: 2.0.0
|
||||
digest: sha256:66a5261f3d68abf63d2bade50e36ac696bec8aac909442d328fd5d395bf4bc21
|
||||
generated: "2025-01-08T17:14:12.432022+08:00"
|
||||
version: 2.0.2
|
||||
digest: sha256:9c84a628df434c4bf23ec10d62ad7ddf4b15957f797b01bbaa492ede33d87003
|
||||
generated: "2025-01-17T15:10:43.589701962+08:00"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
apiVersion: v2
|
||||
appVersion: 2.0.6-rc.1
|
||||
appVersion: 2.0.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: 2.0.6-rc.1
|
||||
version: 2.0.6
|
||||
- name: higress-console
|
||||
repository: "https://higress.io/helm-charts/"
|
||||
version: 2.0.0
|
||||
version: 2.0.2
|
||||
type: application
|
||||
version: 2.0.6-rc.1
|
||||
version: 2.0.6
|
||||
|
||||
@@ -164,6 +164,7 @@ The command removes all the Kubernetes components associated with the chart and
|
||||
| global.enableIstioAPI | bool | `true` | If true, Higress Controller will monitor istio resources as well |
|
||||
| global.enableLDSCache | bool | `true` | |
|
||||
| global.enableProxyProtocol | bool | `false` | |
|
||||
| global.enablePushAllMCPClusters | bool | `true` | |
|
||||
| global.enableSRDS | bool | `true` | |
|
||||
| global.enableStatus | bool | `true` | If true, Higress Controller will update the status field of Ingress resources. When migrating from Nginx Ingress, in order to avoid status field of Ingress objects being overwritten, this parameter needs to be set to false, so Higress won't write the entry IP to the status field of the corresponding Ingress object. |
|
||||
| global.externalIstiod | bool | `false` | Configure a remote cluster data plane controlled by an external istiod. When set to true, istiod is not deployed locally and only a subset of the other discovery charts are enabled. |
|
||||
|
||||
Submodule istio/istio updated: 81a46c581f...ad7d051f38
@@ -881,7 +881,7 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
|
||||
if result.PluginConfig != nil {
|
||||
return result, nil
|
||||
}
|
||||
if !obj.DefaultConfigDisable {
|
||||
if !isBoolValueTrue(obj.DefaultConfigDisable) {
|
||||
result.PluginConfig = obj.DefaultConfig
|
||||
}
|
||||
hasValidRule := false
|
||||
@@ -893,7 +893,7 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
|
||||
}
|
||||
var ruleValues []*_struct.Value
|
||||
for _, rule := range obj.MatchRules {
|
||||
if rule.ConfigDisable {
|
||||
if isBoolValueTrue(rule.ConfigDisable) {
|
||||
continue
|
||||
}
|
||||
if rule.Config == nil {
|
||||
@@ -982,13 +982,17 @@ func (m *IngressConfig) convertIstioWasmPlugin(obj *higressext.WasmPlugin) (*ext
|
||||
}
|
||||
}
|
||||
}
|
||||
if !hasValidRule && obj.DefaultConfigDisable {
|
||||
if !hasValidRule && isBoolValueTrue(obj.DefaultConfigDisable) {
|
||||
return nil, nil
|
||||
}
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
func isBoolValueTrue(b *wrappers.BoolValue) bool {
|
||||
return b != nil && b.Value
|
||||
}
|
||||
|
||||
func (m *IngressConfig) AddOrUpdateWasmPlugin(clusterNamespacedName util.ClusterNamespacedName) {
|
||||
if clusterNamespacedName.Namespace != m.namespace {
|
||||
return
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
## 功能说明
|
||||
# 功能说明
|
||||
`model-mapper`插件实现了基于LLM协议中的model参数路由的功能
|
||||
|
||||
## 配置字段
|
||||
# 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
|
||||
| `modelKey` | string | 选填 | model | 请求body中model参数的位置 |
|
||||
| `modelMapping` | map of string | 选填 | - | AI 模型映射表,用于将请求中的模型名称映射为服务提供商支持模型名称。<br/>1. 支持前缀匹配。例如用 "gpt-3-*" 匹配所有名称以“gpt-3-”开头的模型;<br/>2. 支持使用 "*" 为键来配置通用兜底映射关系;<br/>3. 如果映射的目标名称为空字符串 "",则表示保留原模型名称。 |
|
||||
| `enableOnPathSuffix` | array of string | 选填 | ["/v1/chat/completions"] | 只对这些特定路径后缀的请求生效 ## 运行属性
|
||||
| `enableOnPathSuffix` | array of string | 选填 | ["/v1/chat/completions"] | 只对这些特定路径后缀的请求生效 |
|
||||
|
||||
|
||||
插件执行阶段:认证阶段
|
||||
插件执行优先级:800
|
||||
|
|
||||
## 效果说明
|
||||
|
||||
如下配置
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
## 功能说明
|
||||
# 功能说明
|
||||
`model-router`插件实现了基于LLM协议中的model参数路由的功能
|
||||
|
||||
## 配置字段
|
||||
# 配置字段
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- |
|
||||
|
||||
@@ -26,6 +26,7 @@ proxy_wasm_cc_binary(
|
||||
"@com_google_absl//absl/time",
|
||||
"//common:json_util",
|
||||
"//common:http_util",
|
||||
"//common:regex_util",
|
||||
"//common:rule_util",
|
||||
],
|
||||
)
|
||||
@@ -44,6 +45,7 @@ cc_library(
|
||||
"//common:json_util",
|
||||
"@proxy_wasm_cpp_host//:lib",
|
||||
"//common:http_util_nullvm",
|
||||
"//common:regex_util",
|
||||
"//common:rule_util_nullvm",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -1,31 +1,22 @@
|
||||
---
|
||||
title: 请求屏蔽
|
||||
keywords: [higress,request block]
|
||||
description: 请求屏蔽插件配置参考
|
||||
---
|
||||
|
||||
## 功能说明
|
||||
# 功能说明
|
||||
`request-block`插件实现了基于 URL、请求头等特征屏蔽 HTTP 请求,可以用于防护部分站点资源不对外部暴露
|
||||
|
||||
## 运行属性
|
||||
# 配置字段
|
||||
|
||||
插件执行阶段:`鉴权阶段`
|
||||
插件执行优先级:`320`
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| block_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
|
||||
| block_exact_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要精确屏蔽 URL 的字符串 |
|
||||
| block_regexp_urls | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的正则表达式 |
|
||||
| block_headers | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
|
||||
| block_bodies | array of string | 选填,`block_urls`,`block_exact_urls`,`block_regexp_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
|
||||
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
|
||||
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
|
||||
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
|
||||
|
||||
## 配置字段
|
||||
# 配置示例
|
||||
|
||||
| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 |
|
||||
| -------- | -------- | -------- | -------- | -------- |
|
||||
| block_urls | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽 URL 的字符串 |
|
||||
| block_headers | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Header 的字符串 |
|
||||
| block_bodies | array of string | 选填,`block_urls`,`block_headers`,`block_bodies` 中至少必填一项 | - | 配置用于匹配需要屏蔽请求 Body 的字符串 |
|
||||
| blocked_code | number | 选填 | 403 | 配置请求被屏蔽时返回的 HTTP 状态码 |
|
||||
| blocked_message | string | 选填 | - | 配置请求被屏蔽时返回的 HTTP 应答 Body |
|
||||
| case_sensitive | bool | 选填 | true | 配置匹配时是否区分大小写,默认区分 |
|
||||
|
||||
## 配置示例
|
||||
|
||||
### 屏蔽请求 url 路径
|
||||
## 屏蔽请求 url 路径
|
||||
```yaml
|
||||
block_urls:
|
||||
- swagger.html
|
||||
@@ -40,7 +31,36 @@ curl http://example.com?foo=Bar
|
||||
curl http://exmaple.com/Swagger.html
|
||||
```
|
||||
|
||||
### 屏蔽请求 header
|
||||
## 屏蔽精确匹配的请求 url 路径
|
||||
|
||||
```yaml
|
||||
block_exact_urls:
|
||||
- /swagger.html?foo=bar
|
||||
case_sensitive: false
|
||||
```
|
||||
|
||||
根据该配置,下列请求将被禁止访问:
|
||||
|
||||
```bash
|
||||
curl http://exmaple.com/Swagger.html?foo=Bar
|
||||
```
|
||||
|
||||
## 屏蔽正则匹配的请求 url 路径
|
||||
|
||||
```yaml
|
||||
block_exact_urls:
|
||||
- .*swagger.*
|
||||
case_sensitive: false
|
||||
```
|
||||
|
||||
根据该配置,下列请求将被禁止访问:
|
||||
|
||||
```bash
|
||||
curl http://exmaple.com/Swagger.html?foo=Bar
|
||||
```
|
||||
|
||||
|
||||
## 屏蔽请求 header
|
||||
```yaml
|
||||
block_headers:
|
||||
- example-key
|
||||
@@ -54,9 +74,9 @@ curl http://example.com -H 'example-key: 123'
|
||||
curl http://exmaple.com -H 'my-header: example-value'
|
||||
```
|
||||
|
||||
### 屏蔽请求 body
|
||||
## 屏蔽请求 body
|
||||
```yaml
|
||||
block_bodies:
|
||||
block_bodys:
|
||||
- "hello world"
|
||||
case_sensitive: false
|
||||
```
|
||||
@@ -68,8 +88,30 @@ curl http://example.com -d 'Hello World'
|
||||
curl http://exmaple.com -d 'hello world'
|
||||
```
|
||||
|
||||
## 对特定路由或域名开启
|
||||
```yaml
|
||||
# 使用 _rules_ 字段进行细粒度规则配置
|
||||
_rules_:
|
||||
# 规则一:按路由名称匹配生效
|
||||
- _match_route_:
|
||||
- route-a
|
||||
- route-b
|
||||
block_bodys:
|
||||
- "hello world"
|
||||
# 规则二:按域名匹配生效
|
||||
- _match_domain_:
|
||||
- "*.example.com"
|
||||
- test.com
|
||||
block_urls:
|
||||
- "swagger.html"
|
||||
block_bodys:
|
||||
- "hello world"
|
||||
```
|
||||
此例 `_match_route_` 中指定的 `route-a` 和 `route-b` 即在创建网关路由时填写的路由名称,当匹配到这两个路由时,将使用此段配置;
|
||||
此例 `_match_domain_` 中指定的 `*.example.com` 和 `test.com` 用于匹配请求的域名,当发现域名匹配时,将使用此段配置;
|
||||
配置的匹配生效顺序,将按照 `_rules_` 下规则的排列顺序,匹配第一个规则后生效对应配置,后续规则将被忽略。
|
||||
|
||||
## 请求 Body 大小限制
|
||||
# 请求 Body 大小限制
|
||||
|
||||
当配置了 `block_bodies` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||
当配置了 `block_bodies` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`
|
||||
当配置了 `block_bodys` 时,仅支持小于 32 MB 的请求 Body 进行匹配。若请求 Body 大于此限制,并且不存在匹配到的 `block_urls` 和 `block_headers` 项时,不会对该请求执行屏蔽操作
|
||||
当配置了 `block_bodys` 时,若请求 Body 超过全局配置 DownstreamConnectionBufferLimits,将返回 `413 Payload Too Large`, 请在参数配置页调高此项。注意调高此参数配置后,网关内存使用将有显著增加。
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "extensions/request_block/plugin.h"
|
||||
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#include "absl/strings/str_cat.h"
|
||||
#include "absl/strings/str_join.h"
|
||||
@@ -89,6 +90,48 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
|
||||
LOG_WARN("failed to parse configuration for block_urls.");
|
||||
return false;
|
||||
}
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "block_exact_urls", [&](const json& item) -> bool {
|
||||
auto url = JsonValueAs<std::string>(item);
|
||||
if (url.second != Wasm::Common::JsonParserResultDetail::OK) {
|
||||
LOG_WARN("cannot parse block_exact_urls");
|
||||
return false;
|
||||
}
|
||||
if (rule.case_sensitive) {
|
||||
rule.block_exact_urls.push_back(std::move(url.first.value()));
|
||||
} else {
|
||||
rule.block_exact_urls.push_back(
|
||||
absl::AsciiStrToLower(url.first.value()));
|
||||
}
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse configuration for block_exact_urls.");
|
||||
return false;
|
||||
}
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "block_regexp_urls", [&](const json& item) -> bool {
|
||||
auto url = JsonValueAs<std::string>(item);
|
||||
if (url.second != Wasm::Common::JsonParserResultDetail::OK) {
|
||||
LOG_WARN("cannot parse block_regexp_urls");
|
||||
return false;
|
||||
}
|
||||
std::string regex;
|
||||
if (rule.case_sensitive) {
|
||||
regex = url.first.value();
|
||||
} else {
|
||||
regex = absl::AsciiStrToLower(url.first.value());
|
||||
}
|
||||
auto re = std::make_unique<ReMatcher>(regex);
|
||||
if (!re->error().empty()) {
|
||||
LOG_WARN(re->error());
|
||||
return false;
|
||||
}
|
||||
rule.block_regexp_urls.push_back(std::move(re));
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse configuration for block_regexp_urls.");
|
||||
return false;
|
||||
}
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "block_headers", [&](const json& item) -> bool {
|
||||
auto header = JsonValueAs<std::string>(item);
|
||||
@@ -125,8 +168,28 @@ bool PluginRootContext::parsePluginConfig(const json& configuration,
|
||||
LOG_WARN("failed to parse configuration for block_bodys.");
|
||||
return false;
|
||||
}
|
||||
// compatiable
|
||||
if (!JsonArrayIterate(
|
||||
configuration, "block_bodies", [&](const json& item) -> bool {
|
||||
auto body = JsonValueAs<std::string>(item);
|
||||
if (body.second != Wasm::Common::JsonParserResultDetail::OK) {
|
||||
LOG_WARN("cannot parse block_bodies");
|
||||
return false;
|
||||
}
|
||||
if (rule.case_sensitive) {
|
||||
rule.block_bodys.push_back(std::move(body.first.value()));
|
||||
} else {
|
||||
rule.block_bodys.push_back(
|
||||
absl::AsciiStrToLower(body.first.value()));
|
||||
}
|
||||
return true;
|
||||
})) {
|
||||
LOG_WARN("failed to parse configuration for block_bodies.");
|
||||
return false;
|
||||
}
|
||||
if (rule.block_bodys.empty() && rule.block_headers.empty() &&
|
||||
rule.block_urls.empty()) {
|
||||
rule.block_urls.empty() && rule.block_exact_urls.empty() &&
|
||||
rule.block_regexp_urls.empty()) {
|
||||
LOG_WARN("there is no block rules");
|
||||
return false;
|
||||
}
|
||||
@@ -172,6 +235,18 @@ bool PluginRootContext::checkHeader(const RequestBlockConfigRule& rule,
|
||||
urlstr = absl::AsciiStrToLower(request_url);
|
||||
url = urlstr;
|
||||
}
|
||||
for (const auto& block_url : rule.block_exact_urls) {
|
||||
if (url == block_url) {
|
||||
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& block_url : rule.block_regexp_urls) {
|
||||
if (block_url->match(url)) {
|
||||
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (const auto& block_url : rule.block_urls) {
|
||||
if (absl::StrContains(url, block_url)) {
|
||||
sendLocalResponse(rule.blocked_code, "", rule.blocked_message, {});
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <unordered_map>
|
||||
|
||||
#include "common/http_util.h"
|
||||
#include "common/regex.h"
|
||||
#include "common/route_rule_matcher.h"
|
||||
#define ASSERT(_X) assert(_X)
|
||||
|
||||
@@ -39,11 +40,16 @@ namespace request_block {
|
||||
|
||||
#endif
|
||||
|
||||
using ReMatcher = Wasm::Common::Regex::CompiledGoogleReMatcher;
|
||||
using ReMatcherPtr = std::unique_ptr<ReMatcher>;
|
||||
|
||||
struct RequestBlockConfigRule {
|
||||
int blocked_code = 403;
|
||||
std::string blocked_message;
|
||||
bool case_sensitive = true;
|
||||
std::vector<std::string> block_urls;
|
||||
std::vector<std::string> block_exact_urls;
|
||||
std::vector<ReMatcherPtr> block_regexp_urls;
|
||||
std::vector<std::string> block_headers;
|
||||
std::vector<std::string> block_bodys;
|
||||
};
|
||||
|
||||
@@ -127,6 +127,8 @@ TEST_F(RequestBlockTest, CaseSensitive) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"block_urls": ["?foo=bar", "swagger.html"],
|
||||
"block_exact_urls": ["/hello.html?abc=123"],
|
||||
"block_regexp_urls": [".*monkey.*"],
|
||||
"block_headers": ["headerKey", "headerValue"],
|
||||
"block_bodys": ["Hello World"]
|
||||
})";
|
||||
@@ -150,6 +152,22 @@ TEST_F(RequestBlockTest, CaseSensitive) {
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
|
||||
path_ = "/hello.html?abc=123";
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
|
||||
path_ = "/black/Monkey";
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
|
||||
path_ = "/black/monkey";
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
|
||||
path_ = "";
|
||||
headers_ = {{"headerKey", "123"}};
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(403, testing::_, testing::_,
|
||||
@@ -188,6 +206,8 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
|
||||
"blocked_code": 404,
|
||||
"block_urls": ["?foo=bar", "swagger.html"],
|
||||
"block_headers": ["headerKey", "headerValue"],
|
||||
"block_exact_urls": ["/hello.html?abc=123"],
|
||||
"block_regexp_urls": [".*monkey.*"],
|
||||
"block_bodys": ["Hello World"]
|
||||
})";
|
||||
|
||||
@@ -206,6 +226,24 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
|
||||
path_ = "/Hello.html?abc=123";
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
|
||||
path_ = "/black/Monkey";
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
|
||||
path_ = "/black/monkey";
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::StopIteration);
|
||||
|
||||
path_ = "";
|
||||
headers_ = {{"headerkey", "123"}};
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
|
||||
@@ -232,6 +270,26 @@ TEST_F(RequestBlockTest, CaseInsensitive) {
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
}
|
||||
|
||||
TEST_F(RequestBlockTest, Bodies) {
|
||||
std::string configuration = R"(
|
||||
{
|
||||
"case_sensitive": false,
|
||||
"blocked_code": 404,
|
||||
"block_bodies": ["Hello World"]
|
||||
})";
|
||||
|
||||
config_.set({configuration.data(), configuration.size()});
|
||||
EXPECT_TRUE(root_context_->configure(configuration.size()));
|
||||
|
||||
body_.set("hello world");
|
||||
EXPECT_EQ(context_->onRequestHeaders(0, false),
|
||||
FilterHeadersStatus::Continue);
|
||||
EXPECT_CALL(*mock_context_, sendLocalResponse(404, testing::_, testing::_,
|
||||
testing::_, testing::_));
|
||||
EXPECT_EQ(context_->onRequestBody(11, true),
|
||||
FilterDataStatus::StopIterationNoBuffer);
|
||||
}
|
||||
|
||||
} // namespace request_block
|
||||
} // namespace null_plugin
|
||||
} // namespace proxy_wasm
|
||||
|
||||
@@ -79,11 +79,11 @@ func (c *PluginConfig) FromJson(json gjson.Result, log wrapper.Log) {
|
||||
|
||||
c.StreamResponseTemplate = json.Get("streamResponseTemplate").String()
|
||||
if c.StreamResponseTemplate == "" {
|
||||
c.StreamResponseTemplate = `data:{"id":"from-cache","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}` + "\n\ndata:[DONE]\n\n"
|
||||
c.StreamResponseTemplate = `data:{"id":"from-cache","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"from-cache","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}` + "\n\ndata:[DONE]\n\n"
|
||||
}
|
||||
c.ResponseTemplate = json.Get("responseTemplate").String()
|
||||
if c.ResponseTemplate == "" {
|
||||
c.ResponseTemplate = `{"id":"from-cache","choices":[{"index":0,"message":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}`
|
||||
c.ResponseTemplate = `{"id":"from-cache","choices":[{"index":0,"message":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"from-cache","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}`
|
||||
}
|
||||
|
||||
if json.Get("enableSemanticCache").Exists() {
|
||||
|
||||
@@ -34,7 +34,7 @@ func parseConfig(json gjson.Result, config *AIPromptTemplateConfig, log wrapper.
|
||||
|
||||
func onHttpRequestHeaders(ctx wrapper.HttpContext, config AIPromptTemplateConfig, log wrapper.Log) types.Action {
|
||||
templateEnable, _ := proxywasm.GetHttpRequestHeader("template-enable")
|
||||
if templateEnable != "true" {
|
||||
if templateEnable == "false" {
|
||||
ctx.DontReadRequestBody()
|
||||
return types.ActionContinue
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ type ai360Provider struct {
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *ai360ProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *ai360ProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
type azureProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *azureProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *azureProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.azureServiceUrl == "" {
|
||||
return errors.New("missing azureServiceUrl in provider config")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
type baichuanProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *baichuanProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *baichuanProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -34,16 +34,15 @@ const (
|
||||
|
||||
type baiduProviderInitializer struct{}
|
||||
|
||||
func (g *baiduProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (g *baiduProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.baiduAccessKeyAndSecret == nil || len(config.baiduAccessKeyAndSecret) == 0 {
|
||||
return errors.New("no baiduAccessKeyAndSecret found in provider config")
|
||||
}
|
||||
if config.baiduApiTokenServiceName == "" {
|
||||
return errors.New("no baiduApiTokenServiceName found in provider config")
|
||||
}
|
||||
if !config.failover.enabled {
|
||||
config.useGlobalApiToken = true
|
||||
}
|
||||
// baidu use access key and access secret to refresh apiToken regularly, the apiToken should be accessed globally (via all Wasm VMs)
|
||||
config.useGlobalApiToken = true
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ type claudeTextGenDelta struct {
|
||||
StopSequence *string `json:"stop_sequence"`
|
||||
}
|
||||
|
||||
func (c *claudeProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (c *claudeProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
type cloudflareProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (c *cloudflareProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (c *cloudflareProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
|
||||
type cohereProviderInitializer struct{}
|
||||
|
||||
func (m *cohereProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *cohereProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ const (
|
||||
|
||||
type cozeProviderInitializer struct{}
|
||||
|
||||
func (m *cozeProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *cozeProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ type deeplResponseTranslation struct {
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
func (d *deeplProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (d *deeplProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.targetLang == "" {
|
||||
return errors.New("missing targetLang in deepl provider config")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
type deepseekProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *deepseekProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *deepseekProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const (
|
||||
|
||||
type doubaoProviderInitializer struct{}
|
||||
|
||||
func (m *doubaoProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *doubaoProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -4,14 +4,14 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/google/uuid"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util"
|
||||
"github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper"
|
||||
"github.com/google/uuid"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm"
|
||||
"github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types"
|
||||
"github.com/tidwall/gjson"
|
||||
@@ -551,13 +551,15 @@ func (c *ProviderConfig) OnRequestFailed(activeProvider Provider, ctx wrapper.Ht
|
||||
}
|
||||
|
||||
func (c *ProviderConfig) GetApiTokenInUse(ctx wrapper.HttpContext) string {
|
||||
return ctx.GetContext(c.failover.ctxApiTokenInUse).(string)
|
||||
token, _ := ctx.GetContext(c.failover.ctxApiTokenInUse).(string)
|
||||
return token
|
||||
}
|
||||
|
||||
func (c *ProviderConfig) SetApiTokenInUse(ctx wrapper.HttpContext, log wrapper.Log) {
|
||||
var apiToken string
|
||||
if c.isFailoverEnabled() || c.useGlobalApiToken {
|
||||
// if enable apiToken failover, only use available apiToken
|
||||
// if enable apiToken failover, only use available apiToken from global apiTokens list
|
||||
// or the apiToken need to be accessed globally (via all Wasm VMs, e.g. baidu),
|
||||
apiToken = c.GetGlobalRandomToken(log)
|
||||
} else {
|
||||
apiToken = c.GetRandomToken()
|
||||
|
||||
@@ -28,7 +28,7 @@ const (
|
||||
type geminiProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (g *geminiProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (g *geminiProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ type githubProvider struct {
|
||||
contextCache *contextCache
|
||||
}
|
||||
|
||||
func (m *githubProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *githubProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ const (
|
||||
|
||||
type groqProviderInitializer struct{}
|
||||
|
||||
func (g *groqProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (g *groqProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ type hunyuanChatMessage struct {
|
||||
Content string `json:"Content,omitempty"`
|
||||
}
|
||||
|
||||
func (m *hunyuanProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *hunyuanProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
// 校验hunyuan id 和 key的合法性
|
||||
if len(config.hunyuanAuthId) != hunyuanAuthIdLen || len(config.hunyuanAuthKey) != hunyuanAuthKeyLen {
|
||||
return errors.New("hunyuanAuthId / hunyuanAuthKey is illegal in config file")
|
||||
|
||||
@@ -38,7 +38,7 @@ const (
|
||||
type minimaxProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *minimaxProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *minimaxProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
// If using the chat completion Pro API, a group ID must be set.
|
||||
if minimaxApiTypePro == config.minimaxApiType && config.minimaxGroupId == "" {
|
||||
return errors.New(fmt.Sprintf("missing minimaxGroupId in provider config when minimaxApiType is %s", minimaxApiTypePro))
|
||||
@@ -93,10 +93,6 @@ func (m *minimaxProvider) OnRequestBody(ctx wrapper.HttpContext, apiName ApiName
|
||||
}
|
||||
}
|
||||
|
||||
func (m *minimaxProvider) TransformRequestBodyHeaders(ctx wrapper.HttpContext, apiName ApiName, body []byte, headers http.Header, log wrapper.Log) ([]byte, error) {
|
||||
return m.handleRequestBodyByChatCompletionV2(body, headers, log)
|
||||
}
|
||||
|
||||
// handleRequestBodyByChatCompletionPro processes the request body using the chat completion Pro API.
|
||||
func (m *minimaxProvider) handleRequestBodyByChatCompletionPro(body []byte, log wrapper.Log) (types.Action, error) {
|
||||
request := &chatCompletionRequest{}
|
||||
@@ -109,7 +105,7 @@ func (m *minimaxProvider) handleRequestBodyByChatCompletionPro(body []byte, log
|
||||
_ = util.OverwriteRequestPath(fmt.Sprintf("%s?GroupId=%s", minimaxChatCompletionProPath, m.config.minimaxGroupId))
|
||||
|
||||
if m.config.context == nil {
|
||||
minimaxRequest := m.buildMinimaxChatCompletionV2Request(request, "")
|
||||
minimaxRequest := m.buildMinimaxChatCompletionProRequest(request, "")
|
||||
return types.ActionContinue, replaceJsonRequestBody(minimaxRequest, log)
|
||||
}
|
||||
|
||||
@@ -124,7 +120,7 @@ func (m *minimaxProvider) handleRequestBodyByChatCompletionPro(body []byte, log
|
||||
// Since minimaxChatCompletionV2 (format consistent with OpenAI) and minimaxChatCompletionPro (different format from OpenAI) have different logic for insertHttpContextMessage, we cannot unify them within one provider.
|
||||
// For minimaxChatCompletionPro, we need to manually handle context messages.
|
||||
// minimaxChatCompletionV2 uses the default defaultInsertHttpContextMessage method to insert context messages.
|
||||
minimaxRequest := m.buildMinimaxChatCompletionV2Request(request, content)
|
||||
minimaxRequest := m.buildMinimaxChatCompletionProRequest(request, content)
|
||||
if err := replaceJsonRequestBody(minimaxRequest, log); err != nil {
|
||||
util.ErrorHandler("ai-proxy.minimax.insert_ctx_failed", fmt.Errorf("failed to replace Request body: %v", err))
|
||||
}
|
||||
@@ -135,6 +131,10 @@ func (m *minimaxProvider) handleRequestBodyByChatCompletionPro(body []byte, log
|
||||
return types.ActionContinue, err
|
||||
}
|
||||
|
||||
func (m *minimaxProvider) TransformRequestBodyHeaders(ctx wrapper.HttpContext, apiName ApiName, body []byte, headers http.Header, log wrapper.Log) ([]byte, error) {
|
||||
return m.handleRequestBodyByChatCompletionV2(body, headers, log)
|
||||
}
|
||||
|
||||
// handleRequestBodyByChatCompletionV2 processes the request body using the chat completion V2 API.
|
||||
func (m *minimaxProvider) handleRequestBodyByChatCompletionV2(body []byte, headers http.Header, log wrapper.Log) ([]byte, error) {
|
||||
util.OverwriteRequestPathHeader(headers, minimaxChatCompletionV2Path)
|
||||
@@ -144,15 +144,13 @@ func (m *minimaxProvider) handleRequestBodyByChatCompletionV2(body []byte, heade
|
||||
return sjson.SetBytes(body, "model", mappedModel)
|
||||
}
|
||||
|
||||
// Skip OnStreamingResponseBody() and OnResponseBody() when using original protocol.
|
||||
func (m *minimaxProvider) TransformResponseHeaders(ctx wrapper.HttpContext, apiName ApiName, headers http.Header, log wrapper.Log) {
|
||||
if m.config.protocol == protocolOriginal {
|
||||
ctx.DontReadResponseBody()
|
||||
}
|
||||
|
||||
// Skip OnStreamingResponseBody() and OnResponseBody() when the model corresponds to the chat completion V2 interface.
|
||||
if minimaxApiTypePro != m.config.minimaxApiType {
|
||||
// Skip OnStreamingResponseBody() and OnResponseBody() when using the original protocol
|
||||
// or when the model corresponds to the chat completion V2 interface.
|
||||
if m.config.protocol == protocolOriginal || minimaxApiTypePro != m.config.minimaxApiType {
|
||||
ctx.DontReadResponseBody()
|
||||
} else {
|
||||
headers.Del("Content-Length")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,12 +172,12 @@ func (m *minimaxProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name
|
||||
continue
|
||||
}
|
||||
data = data[6:]
|
||||
var minimaxResp minimaxChatCompletionV2Resp
|
||||
var minimaxResp minimaxChatCompletionProResp
|
||||
if err := json.Unmarshal([]byte(data), &minimaxResp); err != nil {
|
||||
log.Errorf("unable to unmarshal minimax response: %v", err)
|
||||
continue
|
||||
}
|
||||
response := m.responseV2ToOpenAI(&minimaxResp)
|
||||
response := m.responseProToOpenAI(&minimaxResp)
|
||||
responseBody, err := json.Marshal(response)
|
||||
if err != nil {
|
||||
log.Errorf("unable to marshal response: %v", err)
|
||||
@@ -192,21 +190,21 @@ func (m *minimaxProvider) OnStreamingResponseBody(ctx wrapper.HttpContext, name
|
||||
return []byte(modifiedResponseChunk), nil
|
||||
}
|
||||
|
||||
// OnResponseBody handles the final response body from the Minimax service only for requests using the OpenAI protocol and corresponding to the chat completion Pro API.
|
||||
// TransformResponseBody handles the final response body from the Minimax service only for requests using the OpenAI protocol and corresponding to the chat completion Pro API.
|
||||
func (m *minimaxProvider) TransformResponseBody(ctx wrapper.HttpContext, apiName ApiName, body []byte, log wrapper.Log) ([]byte, error) {
|
||||
minimaxResp := &minimaxChatCompletionV2Resp{}
|
||||
minimaxResp := &minimaxChatCompletionProResp{}
|
||||
if err := json.Unmarshal(body, minimaxResp); err != nil {
|
||||
return nil, fmt.Errorf("unable to unmarshal minimax response: %v", err)
|
||||
}
|
||||
if minimaxResp.BaseResp.StatusCode != 0 {
|
||||
return nil, fmt.Errorf("minimax response error, error_code: %d, error_message: %s", minimaxResp.BaseResp.StatusCode, minimaxResp.BaseResp.StatusMsg)
|
||||
}
|
||||
response := m.responseV2ToOpenAI(minimaxResp)
|
||||
response := m.responseProToOpenAI(minimaxResp)
|
||||
return json.Marshal(response)
|
||||
}
|
||||
|
||||
// minimaxChatCompletionV2Request represents the structure of a chat completion V2 request.
|
||||
type minimaxChatCompletionV2Request struct {
|
||||
// minimaxChatCompletionProRequest represents the structure of a chat completion Pro request.
|
||||
type minimaxChatCompletionProRequest struct {
|
||||
Model string `json:"model"`
|
||||
Stream bool `json:"stream,omitempty"`
|
||||
TokensToGenerate int64 `json:"tokens_to_generate,omitempty"`
|
||||
@@ -237,19 +235,17 @@ type minimaxReplyConstraints struct {
|
||||
SenderName string `json:"sender_name"`
|
||||
}
|
||||
|
||||
// minimaxChatCompletionV2Resp represents the structure of a Minimax Chat Completion V2 response.
|
||||
type minimaxChatCompletionV2Resp struct {
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Reply string `json:"reply"`
|
||||
InputSensitive bool `json:"input_sensitive,omitempty"`
|
||||
InputSensitiveType int64 `json:"input_sensitive_type,omitempty"`
|
||||
OutputSensitive bool `json:"output_sensitive,omitempty"`
|
||||
OutputSensitiveType int64 `json:"output_sensitive_type,omitempty"`
|
||||
Choices []minimaxChoice `json:"choices,omitempty"`
|
||||
Usage minimaxUsage `json:"usage,omitempty"`
|
||||
Id string `json:"id"`
|
||||
BaseResp minimaxBaseResp `json:"base_resp"`
|
||||
// minimaxChatCompletionProResp represents the structure of a Minimax Chat Completion Pro response.
|
||||
type minimaxChatCompletionProResp struct {
|
||||
Created int64 `json:"created"`
|
||||
Model string `json:"model"`
|
||||
Reply string `json:"reply"`
|
||||
InputSensitive bool `json:"input_sensitive,omitempty"`
|
||||
OutputSensitive bool `json:"output_sensitive,omitempty"`
|
||||
Choices []minimaxChoice `json:"choices,omitempty"`
|
||||
Usage minimaxUsage `json:"usage,omitempty"`
|
||||
Id string `json:"id"`
|
||||
BaseResp minimaxBaseResp `json:"base_resp"`
|
||||
}
|
||||
|
||||
// minimaxBaseResp contains error status code and details.
|
||||
@@ -267,7 +263,9 @@ type minimaxChoice struct {
|
||||
|
||||
// minimaxUsage represents token usage statistics.
|
||||
type minimaxUsage struct {
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
TotalTokens int64 `json:"total_tokens"`
|
||||
PromptTokens int64 `json:"prompt_tokens"`
|
||||
CompletionTokens int64 `json:"completion_tokens"`
|
||||
}
|
||||
|
||||
func (m *minimaxProvider) parseModel(body []byte) (string, error) {
|
||||
@@ -282,7 +280,7 @@ func (m *minimaxProvider) parseModel(body []byte) (string, error) {
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (m *minimaxProvider) setBotSettings(request *minimaxChatCompletionV2Request, botSettingContent string) {
|
||||
func (m *minimaxProvider) setBotSettings(request *minimaxChatCompletionProRequest, botSettingContent string) {
|
||||
if len(request.BotSettings) == 0 {
|
||||
request.BotSettings = []minimaxBotSetting{
|
||||
{
|
||||
@@ -304,7 +302,7 @@ func (m *minimaxProvider) setBotSettings(request *minimaxChatCompletionV2Request
|
||||
}
|
||||
}
|
||||
|
||||
func (m *minimaxProvider) buildMinimaxChatCompletionV2Request(request *chatCompletionRequest, botSettingContent string) *minimaxChatCompletionV2Request {
|
||||
func (m *minimaxProvider) buildMinimaxChatCompletionProRequest(request *chatCompletionRequest, botSettingContent string) *minimaxChatCompletionProRequest {
|
||||
var messages []minimaxMessage
|
||||
var botSetting []minimaxBotSetting
|
||||
var botName string
|
||||
@@ -343,7 +341,7 @@ func (m *minimaxProvider) buildMinimaxChatCompletionV2Request(request *chatCompl
|
||||
SenderType: senderTypeBot,
|
||||
SenderName: determineName(botName, defaultBotName),
|
||||
}
|
||||
result := &minimaxChatCompletionV2Request{
|
||||
result := &minimaxChatCompletionProRequest{
|
||||
Model: request.Model,
|
||||
Stream: request.Stream,
|
||||
TokensToGenerate: int64(request.MaxTokens),
|
||||
@@ -359,7 +357,7 @@ func (m *minimaxProvider) buildMinimaxChatCompletionV2Request(request *chatCompl
|
||||
return result
|
||||
}
|
||||
|
||||
func (m *minimaxProvider) responseV2ToOpenAI(response *minimaxChatCompletionV2Resp) *chatCompletionResponse {
|
||||
func (m *minimaxProvider) responseProToOpenAI(response *minimaxChatCompletionProResp) *chatCompletionResponse {
|
||||
var choices []chatCompletionChoice
|
||||
messageIndex := 0
|
||||
for _, choice := range response.Choices {
|
||||
@@ -384,7 +382,9 @@ func (m *minimaxProvider) responseV2ToOpenAI(response *minimaxChatCompletionV2Re
|
||||
Model: response.Model,
|
||||
Choices: choices,
|
||||
Usage: usage{
|
||||
TotalTokens: int(response.Usage.TotalTokens),
|
||||
TotalTokens: int(response.Usage.TotalTokens),
|
||||
PromptTokens: int(response.Usage.PromptTokens),
|
||||
CompletionTokens: int(response.Usage.CompletionTokens),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ const (
|
||||
|
||||
type mistralProviderInitializer struct{}
|
||||
|
||||
func (m *mistralProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *mistralProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
type moonshotProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *moonshotProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *moonshotProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.moonshotFileId != "" && config.context != nil {
|
||||
return errors.New("moonshotFileId and context cannot be configured at the same time")
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ const (
|
||||
type ollamaProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *ollamaProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *ollamaProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.ollamaServerHost == "" {
|
||||
return errors.New("missing ollamaServerHost in provider config")
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ const (
|
||||
type openaiProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *openaiProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *openaiProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ const (
|
||||
)
|
||||
|
||||
type providerInitializer interface {
|
||||
ValidateConfig(ProviderConfig) error
|
||||
ValidateConfig(*ProviderConfig) error
|
||||
CreateProvider(ProviderConfig) (Provider, error)
|
||||
}
|
||||
|
||||
@@ -405,7 +405,7 @@ func (c *ProviderConfig) Validate() error {
|
||||
if !has {
|
||||
return errors.New("unknown provider type: " + c.typ)
|
||||
}
|
||||
if err := initializer.ValidateConfig(*c); err != nil {
|
||||
if err := initializer.ValidateConfig(c); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
@@ -42,7 +42,7 @@ const (
|
||||
type qwenProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *qwenProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *qwenProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if len(config.qwenFileIds) != 0 && config.context != nil {
|
||||
return errors.New("qwenFileIds and context cannot be configured at the same time")
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ type sparkStreamResponse struct {
|
||||
Created int64 `json:"created"`
|
||||
}
|
||||
|
||||
func (i *sparkProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (i *sparkProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ const (
|
||||
type stepfunProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *stepfunProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *stepfunProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ const (
|
||||
|
||||
type togetherAIProviderInitializer struct{}
|
||||
|
||||
func (m *togetherAIProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *togetherAIProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const (
|
||||
type yiProviderInitializer struct {
|
||||
}
|
||||
|
||||
func (m *yiProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *yiProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const (
|
||||
|
||||
type zhipuAiProviderInitializer struct{}
|
||||
|
||||
func (m *zhipuAiProviderInitializer) ValidateConfig(config ProviderConfig) error {
|
||||
func (m *zhipuAiProviderInitializer) ValidateConfig(config *ProviderConfig) error {
|
||||
if config.apiTokens == nil || len(config.apiTokens) == 0 {
|
||||
return errors.New("no apiToken found in provider config")
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -215,35 +216,51 @@ func onHttpStreamingResponseBody(ctx wrapper.HttpContext, config QuotaConfig, da
|
||||
if chatMode == ChatModeNone || chatMode == ChatModeAdmin {
|
||||
return data
|
||||
}
|
||||
var inputToken, outputToken int64
|
||||
var consumer string
|
||||
if inputToken, outputToken, ok := getUsage(data); ok {
|
||||
ctx.SetContext("input_token", inputToken)
|
||||
ctx.SetContext("output_token", outputToken)
|
||||
}
|
||||
|
||||
// chat completion mode
|
||||
if !endOfStream {
|
||||
return data
|
||||
}
|
||||
inputTokenStr, err := proxywasm.GetProperty([]string{"filter_state", "wasm.input_token"})
|
||||
if err != nil {
|
||||
|
||||
if ctx.GetContext("input_token") == nil || ctx.GetContext("output_token") == nil || ctx.GetContext("consumer") == nil {
|
||||
return data
|
||||
}
|
||||
outputTokenStr, err := proxywasm.GetProperty([]string{"filter_state", "wasm.output_token"})
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
inputToken, err := strconv.Atoi(string(inputTokenStr))
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
outputToken, err := strconv.Atoi(string(outputTokenStr))
|
||||
if err != nil {
|
||||
return data
|
||||
}
|
||||
consumer, ok := ctx.GetContext("consumer").(string)
|
||||
if ok {
|
||||
totalToken := int(inputToken + outputToken)
|
||||
log.Debugf("update consumer:%s, totalToken:%d", consumer, totalToken)
|
||||
config.redisClient.DecrBy(config.RedisKeyPrefix+consumer, totalToken, nil)
|
||||
}
|
||||
|
||||
inputToken = ctx.GetContext("input_token").(int64)
|
||||
outputToken = ctx.GetContext("output_token").(int64)
|
||||
consumer = ctx.GetContext("consumer").(string)
|
||||
totalToken := int(inputToken + outputToken)
|
||||
log.Debugf("update consumer:%s, totalToken:%d", consumer, totalToken)
|
||||
config.redisClient.DecrBy(config.RedisKeyPrefix+consumer, totalToken, nil)
|
||||
return data
|
||||
}
|
||||
|
||||
func getUsage(data []byte) (inputTokenUsage int64, outputTokenUsage int64, ok bool) {
|
||||
chunks := bytes.Split(bytes.TrimSpace(data), []byte("\n\n"))
|
||||
for _, chunk := range chunks {
|
||||
// the feature strings are used to identify the usage data, like:
|
||||
// {"model":"gpt2","usage":{"prompt_tokens":1,"completion_tokens":1}}
|
||||
if !bytes.Contains(chunk, []byte("prompt_tokens")) || !bytes.Contains(chunk, []byte("completion_tokens")) {
|
||||
continue
|
||||
}
|
||||
inputTokenObj := gjson.GetBytes(chunk, "usage.prompt_tokens")
|
||||
outputTokenObj := gjson.GetBytes(chunk, "usage.completion_tokens")
|
||||
if inputTokenObj.Exists() && outputTokenObj.Exists() {
|
||||
inputTokenUsage = inputTokenObj.Int()
|
||||
outputTokenUsage = outputTokenObj.Int()
|
||||
ok = true
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func deniedNoKeyAuthData() types.Action {
|
||||
util.SendResponse(http.StatusUnauthorized, "ai-quota.no_key", "text/plain", "Request denied by ai quota check. No Key Authentication information found.")
|
||||
return types.ActionContinue
|
||||
|
||||
@@ -41,9 +41,9 @@ const (
|
||||
LowRisk = "low"
|
||||
NoRisk = "none"
|
||||
|
||||
OpenAIResponseFormat = `{"id": "%s","object":"chat.completion","model":"%s","choices":[{"index":0,"message":{"role":"assistant","content":"%s"},"logprobs":null,"finish_reason":"stop"}]}`
|
||||
OpenAIStreamResponseChunk = `data:{"id":"%s","object":"chat.completion.chunk","model":"%s","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"logprobs":null,"finish_reason":null}]}`
|
||||
OpenAIStreamResponseEnd = `data:{"id":"%s","object":"chat.completion.chunk","model":"%s","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}]}`
|
||||
OpenAIResponseFormat = `{"id": "%s","object":"chat.completion","model":"from-security-guard","choices":[{"index":0,"message":{"role":"assistant","content":"%s"},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}`
|
||||
OpenAIStreamResponseChunk = `data:{"id":"%s","object":"chat.completion.chunk","model":"from-security-guard","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"logprobs":null,"finish_reason":null}]}`
|
||||
OpenAIStreamResponseEnd = `data:{"id":"%s","object":"chat.completion.chunk","model":"from-security-guard","choices":[{"index":0,"delta":{},"logprobs":null,"finish_reason":"stop"}],"usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}`
|
||||
OpenAIStreamResponseFormat = OpenAIStreamResponseChunk + "\n\n" + OpenAIStreamResponseEnd + "\n\n" + `data: [DONE]`
|
||||
|
||||
DefaultRequestCheckService = "llm_query_moderation"
|
||||
@@ -262,8 +262,6 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body []
|
||||
log.Debugf("checking request body...")
|
||||
startTime := time.Now().UnixMilli()
|
||||
content := gjson.GetBytes(body, config.requestContentJsonPath).String()
|
||||
model := gjson.GetBytes(body, "model").String()
|
||||
ctx.SetContext("requestModel", model)
|
||||
log.Debugf("Raw request content is: %s", content)
|
||||
if len(content) == 0 {
|
||||
log.Info("request content is empty. skip")
|
||||
@@ -308,11 +306,11 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body []
|
||||
proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "application/json"}}, []byte(marshalledDenyMessage), -1)
|
||||
} else if gjson.GetBytes(body, "stream").Bool() {
|
||||
randomID := generateRandomID()
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, marshalledDenyMessage, randomID, model))
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, marshalledDenyMessage, randomID))
|
||||
proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "text/event-stream;charset=UTF-8"}}, jsonData, -1)
|
||||
} else {
|
||||
randomID := generateRandomID()
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, model, marshalledDenyMessage))
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, marshalledDenyMessage))
|
||||
proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1)
|
||||
}
|
||||
ctx.DontReadResponseBody()
|
||||
@@ -369,15 +367,6 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body []
|
||||
return types.ActionPause
|
||||
}
|
||||
|
||||
func convertHeaders(hs [][2]string) map[string][]string {
|
||||
ret := make(map[string][]string)
|
||||
for _, h := range hs {
|
||||
k, v := strings.ToLower(h[0]), h[1]
|
||||
ret[k] = append(ret[k], v)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func onHttpResponseHeaders(ctx wrapper.HttpContext, config AISecurityConfig, log wrapper.Log) types.Action {
|
||||
if !config.checkResponse {
|
||||
log.Debugf("response checking is disabled")
|
||||
@@ -398,7 +387,6 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AISecurityConfig, body [
|
||||
startTime := time.Now().UnixMilli()
|
||||
contentType, _ := proxywasm.GetHttpResponseHeader("content-type")
|
||||
isStreamingResponse := strings.Contains(contentType, "event-stream")
|
||||
model := ctx.GetStringContext("requestModel", "unknown")
|
||||
var content string
|
||||
if isStreamingResponse {
|
||||
content = extractMessageFromStreamingBody(body, config.responseStreamContentJsonPath)
|
||||
@@ -449,11 +437,11 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AISecurityConfig, body [
|
||||
proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "application/json"}}, []byte(marshalledDenyMessage), -1)
|
||||
} else if isStreamingResponse {
|
||||
randomID := generateRandomID()
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, marshalledDenyMessage, randomID, model))
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, marshalledDenyMessage, randomID))
|
||||
proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "text/event-stream;charset=UTF-8"}}, jsonData, -1)
|
||||
} else {
|
||||
randomID := generateRandomID()
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, model, marshalledDenyMessage))
|
||||
jsonData := []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, marshalledDenyMessage))
|
||||
proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1)
|
||||
}
|
||||
config.incrementCounter("ai_sec_response_deny", 1)
|
||||
|
||||
@@ -38,7 +38,7 @@ Attribute 配置说明:
|
||||
`value_source` 的各种取值含义如下:
|
||||
|
||||
- `fixed_value`:固定值
|
||||
- `requeset_header` : attrribute 值通过 http 请求头获取,value 配置为 header key
|
||||
- `request_header` : attrribute 值通过 http 请求头获取,value 配置为 header key
|
||||
- `request_body` :attrribute 值通过请求 body 获取,value 配置格式为 gjson 的 jsonpath
|
||||
- `response_header` :attrribute 值通过 http 响应头获取,value 配置为header key
|
||||
- `response_body` :attrribute 值通过响应 body 获取,value 配置格式为 gjson 的 jsonpath
|
||||
|
||||
@@ -38,7 +38,7 @@ Attribute Configuration instructions:
|
||||
The meanings of various values for `value_source` are as follows:
|
||||
|
||||
- `fixed_value`: fixed value
|
||||
- `requeset_header`: The attrribute is obtained through the http request header
|
||||
- `request_header`: The attrribute is obtained through the http request header
|
||||
- `request_body`: The attrribute is obtained through the http request body
|
||||
- `response_header`: The attrribute is obtained through the http response header
|
||||
- `response_body`: The attrribute is obtained through the http response body
|
||||
|
||||
@@ -36,6 +36,7 @@ const (
|
||||
RouteName = "route"
|
||||
ClusterName = "cluster"
|
||||
APIName = "api"
|
||||
ConsumerKey = "x-mse-consumer"
|
||||
|
||||
// Source Type
|
||||
FixedValue = "fixed_value"
|
||||
@@ -81,8 +82,8 @@ type AIStatisticsConfig struct {
|
||||
shouldBufferStreamingBody bool
|
||||
}
|
||||
|
||||
func generateMetricName(route, cluster, model, metricName string) string {
|
||||
return fmt.Sprintf("route.%s.upstream.%s.model.%s.metric.%s", route, cluster, model, metricName)
|
||||
func generateMetricName(route, cluster, model, consumer, metricName string) string {
|
||||
return fmt.Sprintf("route.%s.upstream.%s.model.%s.consumer.%s.metric.%s", route, cluster, model, consumer, metricName)
|
||||
}
|
||||
|
||||
func getRouteName() (string, error) {
|
||||
@@ -115,6 +116,9 @@ func getClusterName() (string, error) {
|
||||
}
|
||||
|
||||
func (config *AIStatisticsConfig) incrementCounter(metricName string, inc uint64) {
|
||||
if inc == 0 {
|
||||
return
|
||||
}
|
||||
counter, ok := config.counterMetrics[metricName]
|
||||
if !ok {
|
||||
counter = proxywasm.DefineCounterMetric(metricName)
|
||||
@@ -158,6 +162,9 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config AIStatisticsConfig, lo
|
||||
ctx.SetContext(ClusterName, cluster)
|
||||
ctx.SetUserAttribute(APIName, api)
|
||||
ctx.SetContext(StatisticsRequestStartTime, time.Now().UnixMilli())
|
||||
if consumer, _ := proxywasm.GetHttpRequestHeader(ConsumerKey); consumer != "" {
|
||||
ctx.SetContext(ConsumerKey, consumer)
|
||||
}
|
||||
|
||||
// Set user defined log & span attributes which type is fixed_value
|
||||
setAttributeBySource(ctx, config, FixedValue, nil, log)
|
||||
@@ -388,6 +395,7 @@ func writeMetric(ctx wrapper.HttpContext, config AIStatisticsConfig, log wrapper
|
||||
var ok bool
|
||||
var route, cluster, model string
|
||||
var inputToken, outputToken uint64
|
||||
consumer := ctx.GetStringContext(ConsumerKey, "none")
|
||||
route, ok = ctx.GetContext(RouteName).(string)
|
||||
if !ok {
|
||||
log.Warnf("RouteName typd assert failed, skip metric record")
|
||||
@@ -421,8 +429,8 @@ func writeMetric(ctx wrapper.HttpContext, config AIStatisticsConfig, log wrapper
|
||||
log.Warnf("inputToken and outputToken cannot equal to 0, skip metric record")
|
||||
return
|
||||
}
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, InputToken), inputToken)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, OutputToken), outputToken)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, consumer, InputToken), inputToken)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, consumer, OutputToken), outputToken)
|
||||
|
||||
// Generate duration metrics
|
||||
var llmFirstTokenDuration, llmServiceDuration uint64
|
||||
@@ -433,8 +441,8 @@ func writeMetric(ctx wrapper.HttpContext, config AIStatisticsConfig, log wrapper
|
||||
log.Warnf("LLMFirstTokenDuration typd assert failed")
|
||||
return
|
||||
}
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, LLMFirstTokenDuration), llmFirstTokenDuration)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, LLMStreamDurationCount), 1)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, consumer, LLMFirstTokenDuration), llmFirstTokenDuration)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, consumer, LLMStreamDurationCount), 1)
|
||||
}
|
||||
if ctx.GetUserAttribute(LLMServiceDuration) != nil {
|
||||
llmServiceDuration, ok = convertToUInt(ctx.GetUserAttribute(LLMServiceDuration))
|
||||
@@ -442,8 +450,8 @@ func writeMetric(ctx wrapper.HttpContext, config AIStatisticsConfig, log wrapper
|
||||
log.Warnf("LLMServiceDuration typd assert failed")
|
||||
return
|
||||
}
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, LLMServiceDuration), llmServiceDuration)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, LLMDurationCount), 1)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, consumer, LLMServiceDuration), llmServiceDuration)
|
||||
config.incrementCounter(generateMetricName(route, cluster, model, consumer, LLMDurationCount), 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ description: 前端灰度插件配置参考
|
||||
| `localStorageGrayKey` | string | 非必填 | - | 使用JWT鉴权方式,用户ID的唯一标识来自`localStorage`中,如果配置了当前参数,则`grayKey`失效 |
|
||||
| `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` |
|
||||
| `userStickyMaxAge` | int | 非必填 | 172800 | 用户粘滞的时长:单位为秒,默认为`172800`,2天时间 |
|
||||
| `includePathPrefixes` | array of strings | 非必填 | - | 强制处理的路径。例如,在 微前端 场景下,XHR 接口如: `/resource/xxx`本质是一个资源请求,需要走插件转发逻辑。 |
|
||||
| `skippedPathPrefixes` | array of strings | 非必填 | - | 用于排除特定路径,避免当前插件处理这些请求。例如,在 rewrite 场景下,XHR 接口请求 `/api/xxx` 如果经过插件转发逻辑,可能会导致非预期的结果。 |
|
||||
| `skippedByHeaders` | map of string to string | 非必填 | - | 用于通过请求头过滤,指定哪些请求不被当前插件
|
||||
处理。`skippedPathPrefixes` 的优先级高于当前配置,且页面HTML请求不受本配置的影响。若本配置为空,默认会判断`sec-fetch-mode=cors`以及`upgrade=websocket`两个header头,进行过滤 |
|
||||
|
||||
@@ -64,6 +64,7 @@ type GrayConfig struct {
|
||||
BackendGrayTag string
|
||||
Injection *Injection
|
||||
SkippedPathPrefixes []string
|
||||
IncludePathPrefixes []string
|
||||
SkippedByHeaders map[string]string
|
||||
}
|
||||
|
||||
@@ -97,6 +98,7 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) {
|
||||
grayConfig.Html = json.Get("html").String()
|
||||
grayConfig.SkippedPathPrefixes = convertToStringList(json.Get("skippedPathPrefixes").Array())
|
||||
grayConfig.SkippedByHeaders = convertToStringMap(json.Get("skippedByHeaders"))
|
||||
grayConfig.IncludePathPrefixes = convertToStringList(json.Get("includePathPrefixes").Array())
|
||||
|
||||
if grayConfig.UserStickyMaxAge == "" {
|
||||
// 默认值2天
|
||||
|
||||
@@ -64,7 +64,13 @@ func IsRequestSkippedByHeaders(grayConfig config.GrayConfig) bool {
|
||||
}
|
||||
|
||||
func IsGrayEnabled(grayConfig config.GrayConfig, requestPath string) bool {
|
||||
// 当前路径中前缀为 SkipedRoute,则不走插件逻辑
|
||||
for _, prefix := range grayConfig.IncludePathPrefixes {
|
||||
if strings.HasPrefix(requestPath, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 当前路径中前缀为 SkippedPathPrefixes,则不走插件逻辑
|
||||
for _, prefix := range grayConfig.SkippedPathPrefixes {
|
||||
if strings.HasPrefix(requestPath, prefix) {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user