From 1a53c7b4d384905b815d7cd7f01f98dd14cfe83d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Fri, 11 Oct 2024 11:39:46 +0800 Subject: [PATCH 01/64] fix mcpbridge endpoint port (#1382) --- registry/direct/watcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/registry/direct/watcher.go b/registry/direct/watcher.go index f523f1f06a..8ad5f858c2 100644 --- a/registry/direct/watcher.go +++ b/registry/direct/watcher.go @@ -152,7 +152,7 @@ func (w *watcher) generateServiceEntry(host string) *v1alpha3.ServiceEntry { } endpoint = &v1alpha3.WorkloadEntry{ Address: pair[0], - Ports: map[string]uint32{"http": uint32(port)}, + Ports: map[string]uint32{w.Protocol: uint32(port)}, } } else if w.Type == string(registry.DNS) { if !domainRegex.MatchString(ep) { From 952c9ec5dc7f87db307bc02fca07a1a31787476a Mon Sep 17 00:00:00 2001 From: rinfx <893383980@qq.com> Date: Mon, 14 Oct 2024 12:45:53 +0800 Subject: [PATCH 02/64] Ai proxy support coze (#1387) --- plugins/wasm-go/extensions/ai-proxy/README.md | 12 +++++ .../wasm-go/extensions/ai-proxy/README_EN.md | 10 +++++ .../extensions/ai-proxy/provider/coze.go | 44 +++++++++++++++++++ .../extensions/ai-proxy/provider/provider.go | 2 + 4 files changed, 68 insertions(+) create mode 100644 plugins/wasm-go/extensions/ai-proxy/provider/coze.go diff --git a/plugins/wasm-go/extensions/ai-proxy/README.md b/plugins/wasm-go/extensions/ai-proxy/README.md index 51156a81d1..2a8a838b1d 100644 --- a/plugins/wasm-go/extensions/ai-proxy/README.md +++ b/plugins/wasm-go/extensions/ai-proxy/README.md @@ -669,6 +669,18 @@ provider: timeout: 1200000 ``` +### 使用 original 协议代理 Coze 应用 + +**配置信息** + +```yaml +provider: + type: coze + apiTokens: + - YOUR_COZE_API_KEY + protocol: original +``` + ### 使用月之暗面配合其原生的文件上下文 提前上传文件至月之暗面,以文件内容作为上下文使用其 AI 服务。 diff --git a/plugins/wasm-go/extensions/ai-proxy/README_EN.md b/plugins/wasm-go/extensions/ai-proxy/README_EN.md index 57ef01193f..e34546a4e5 100644 --- a/plugins/wasm-go/extensions/ai-proxy/README_EN.md +++ b/plugins/wasm-go/extensions/ai-proxy/README_EN.md @@ -656,6 +656,16 @@ providers: timeout: 1200000 ``` +### Using original Protocol Proxy for Coze applications + +```yaml +provider: + type: coze + apiTokens: + - YOUR_COZE_API_KEY + protocol: original +``` + ### Utilizing Moonshot with its Native File Context Upload files to Moonshot in advance and use its AI services based on file content. diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/coze.go b/plugins/wasm-go/extensions/ai-proxy/provider/coze.go new file mode 100644 index 0000000000..c163c95661 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-proxy/provider/coze.go @@ -0,0 +1,44 @@ +package provider + +import ( + "errors" + + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-proxy/util" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" +) + +const ( + cozeDomain = "api.coze.cn" +) + +type cozeProviderInitializer struct{} + +func (m *cozeProviderInitializer) ValidateConfig(config ProviderConfig) error { + if config.apiTokens == nil || len(config.apiTokens) == 0 { + return errors.New("no apiToken found in provider config") + } + return nil +} + +func (m *cozeProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) { + return &cozeProvider{ + config: config, + contextCache: createContextCache(&config), + }, nil +} + +type cozeProvider struct { + config ProviderConfig + contextCache *contextCache +} + +func (m *cozeProvider) GetProviderType() string { + return providerTypeCoze +} + +func (m *cozeProvider) OnRequestHeaders(ctx wrapper.HttpContext, apiName ApiName, log wrapper.Log) (types.Action, error) { + _ = util.OverwriteRequestHost(cozeDomain) + _ = util.OverwriteRequestAuthorization("Bearer " + m.config.GetRandomToken()) + return types.ActionContinue, nil +} diff --git a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go index facd8bb283..c9d59fd035 100644 --- a/plugins/wasm-go/extensions/ai-proxy/provider/provider.go +++ b/plugins/wasm-go/extensions/ai-proxy/provider/provider.go @@ -42,6 +42,7 @@ const ( providerTypeMistral = "mistral" providerTypeCohere = "cohere" providerTypeDoubao = "doubao" + providerTypeCoze = "coze" protocolOpenAI = "openai" protocolOriginal = "original" @@ -101,6 +102,7 @@ var ( providerTypeMistral: &mistralProviderInitializer{}, providerTypeCohere: &cohereProviderInitializer{}, providerTypeDoubao: &doubaoProviderInitializer{}, + providerTypeCoze: &cozeProviderInitializer{}, } ) From 04ce776f14086008424f252bcbfe58eda5dab554 Mon Sep 17 00:00:00 2001 From: Kent Dong Date: Mon, 14 Oct 2024 18:50:45 +0800 Subject: [PATCH 03/64] feat: Support route fallback by default (#1381) --- helm/core/templates/fallback-envoyfilter.yaml | 22 +++++++++++++++++++ helm/core/values.yaml | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 helm/core/templates/fallback-envoyfilter.yaml diff --git a/helm/core/templates/fallback-envoyfilter.yaml b/helm/core/templates/fallback-envoyfilter.yaml new file mode 100644 index 0000000000..567ee09256 --- /dev/null +++ b/helm/core/templates/fallback-envoyfilter.yaml @@ -0,0 +1,22 @@ +apiVersion: networking.istio.io/v1alpha3 +kind: EnvoyFilter +metadata: + name: {{ include "gateway.name" . }}-global-custom-response + namespace: {{ .Release.Namespace }} + labels: + {{- include "gateway.labels" . | nindent 4}} +spec: + configPatches: + - applyTo: HTTP_FILTER + match: + context: GATEWAY + listener: + filterChain: + filter: + name: envoy.filters.network.http_connection_manager + patch: + operation: INSERT_FIRST + value: + name: envoy.filters.http.custom_response + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.custom_response.v3.CustomResponse \ No newline at end of file diff --git a/helm/core/values.yaml b/helm/core/values.yaml index ce08899207..39582f748d 100644 --- a/helm/core/values.yaml +++ b/helm/core/values.yaml @@ -26,7 +26,7 @@ global: autoscalingv2API: true local: false # When deploying to a local cluster (e.g.: kind cluster), set this to true. kind: false # Deprecated. Please use "global.local" instead. Will be removed later. - enableIstioAPI: false + enableIstioAPI: true enableGatewayAPI: false # Deprecated enableHigressIstio: false From 0a112d1a1e22a2b5122d65ab85c792f540c4cbb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 15 Oct 2024 11:50:43 +0800 Subject: [PATCH 04/64] fix mcp service port protocol name (#1383) --- registry/direct/watcher.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/registry/direct/watcher.go b/registry/direct/watcher.go index 8ad5f858c2..533c80d63f 100644 --- a/registry/direct/watcher.go +++ b/registry/direct/watcher.go @@ -133,6 +133,7 @@ var domainRegex = regexp.MustCompile(`^(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA- func (w *watcher) generateServiceEntry(host string) *v1alpha3.ServiceEntry { endpoints := make([]*v1alpha3.WorkloadEntry, 0) + protocol := string(common.ParseProtocol(w.Protocol)) for _, ep := range strings.Split(w.Domain, common.CommaSeparator) { var endpoint *v1alpha3.WorkloadEntry if w.Type == string(registry.Static) { @@ -152,7 +153,7 @@ func (w *watcher) generateServiceEntry(host string) *v1alpha3.ServiceEntry { } endpoint = &v1alpha3.WorkloadEntry{ Address: pair[0], - Ports: map[string]uint32{w.Protocol: uint32(port)}, + Ports: map[string]uint32{protocol: uint32(port)}, } } else if w.Type == string(registry.DNS) { if !domainRegex.MatchString(ep) { @@ -175,8 +176,8 @@ func (w *watcher) generateServiceEntry(host string) *v1alpha3.ServiceEntry { var ports []*v1alpha3.ServicePort ports = append(ports, &v1alpha3.ServicePort{ Number: w.Port, - Name: w.Protocol, - Protocol: string(common.ParseProtocol(w.Protocol)), + Name: protocol, + Protocol: protocol, }) se := &v1alpha3.ServiceEntry{ Hosts: []string{host}, From 85f8eb51660343c9b740e66148d497a16d945c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 15 Oct 2024 20:52:03 +0800 Subject: [PATCH 05/64] key-auth consumer support set independent key source (#1392) --- .../wasm-cpp/extensions/key_auth/plugin.cc | 204 +++++++++++++----- plugins/wasm-cpp/extensions/key_auth/plugin.h | 12 +- .../extensions/key_auth/plugin_test.cc | 63 +++++- 3 files changed, 218 insertions(+), 61 deletions(-) diff --git a/plugins/wasm-cpp/extensions/key_auth/plugin.cc b/plugins/wasm-cpp/extensions/key_auth/plugin.cc index a88b6bda7a..ad111760e8 100644 --- a/plugins/wasm-cpp/extensions/key_auth/plugin.cc +++ b/plugins/wasm-cpp/extensions/key_auth/plugin.cc @@ -42,10 +42,7 @@ static RegisterContextFactory register_KeyAuth(CONTEXT_FACTORY(PluginContext), namespace { -void deniedNoKeyAuthData(const std::string& realm) { - sendLocalResponse(401, "No API key found in request", "", - {{"WWW-Authenticate", absl::StrCat("Key realm=", realm)}}); -} +const std::string OriginalAuthKey("X-HI-ORIGINAL-AUTH"); void deniedInvalidCredentials(const std::string& realm) { sendLocalResponse(401, "Request denied by Key Auth check. Invalid API key", @@ -84,6 +81,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, } if (!JsonArrayIterate( configuration, "consumers", [&](const json& consumer) -> bool { + Consumer c; auto item = consumer.find("name"); if (item == consumer.end()) { LOG_WARN("can't find 'name' field in consumer."); @@ -94,6 +92,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, !name.first) { return false; } + c.name = name.first.value(); item = consumer.find("credential"); if (item == consumer.end()) { LOG_WARN("can't find 'credential' field in consumer."); @@ -104,6 +103,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, !credential.first) { return false; } + c.credential = credential.first.value(); if (rule.credential_to_name.find(credential.first.value()) != rule.credential_to_name.end()) { LOG_WARN(absl::StrCat("duplicate consumer credential: ", @@ -113,15 +113,59 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, rule.credentials.insert(credential.first.value()); rule.credential_to_name.emplace( std::make_pair(credential.first.value(), name.first.value())); + item = consumer.find("keys"); + if (item != consumer.end()) { + c.keys = std::vector{OriginalAuthKey}; + if (!JsonArrayIterate( + consumer, "keys", [&](const json& key_json) -> bool { + auto key = JsonValueAs(key_json); + if (key.second != + Wasm::Common::JsonParserResultDetail::OK) { + return false; + } + c.keys->push_back(key.first.value()); + return true; + })) { + LOG_WARN("failed to parse configuration for consumer keys."); + return false; + } + item = consumer.find("in_query"); + if (item != consumer.end()) { + auto in_query = JsonValueAs(item.value()); + if (in_query.second != + Wasm::Common::JsonParserResultDetail::OK || + !in_query.first) { + LOG_WARN( + "failed to parse 'in_query' field in consumer " + "configuration."); + return false; + } + c.in_query = in_query.first; + } + item = consumer.find("in_header"); + if (item != consumer.end()) { + auto in_header = JsonValueAs(item.value()); + if (in_header.second != + Wasm::Common::JsonParserResultDetail::OK || + !in_header.first) { + LOG_WARN( + "failed to parse 'in_header' field in consumer " + "configuration."); + return false; + } + c.in_header = in_header.first; + } + } + rule.consumers.push_back(std::move(c)); return true; })) { LOG_WARN("failed to parse configuration for credentials."); return false; } - if (rule.credentials.empty()) { - LOG_INFO("at least one credential has to be configured for a rule."); - return false; - } + // if (rule.credentials.empty()) { + // LOG_INFO("at least one credential has to be configured for a rule."); + // return false; + // } if (!JsonArrayIterate(configuration, "keys", [&](const json& item) -> bool { auto key = JsonValueAs(item); if (key.second != Wasm::Common::JsonParserResultDetail::OK) { @@ -137,6 +181,7 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, LOG_WARN("at least one key has to be configured for a rule."); return false; } + rule.keys.push_back(OriginalAuthKey); auto it = configuration.find("realm"); if (it != configuration.end()) { auto realm_string = JsonValueAs(it.value()); @@ -175,36 +220,102 @@ bool PluginRootContext::parsePluginConfig(const json& configuration, bool PluginRootContext::checkPlugin( const KeyAuthConfigRule& rule, const std::optional>& allow_set) { - auto credential = extractCredential(rule); - if (credential.empty()) { - LOG_DEBUG("empty credential"); - deniedNoKeyAuthData(rule.realm); - return false; + if (rule.consumers.empty()) { + for (const auto& key : rule.keys) { + auto credential = extractCredential(rule.in_header, rule.in_query, key); + if (credential.empty()) { + LOG_DEBUG("empty credential for key: " + key); + continue; + } + + auto auth_credential_iter = rule.credentials.find(credential); + if (auth_credential_iter == rule.credentials.end()) { + LOG_DEBUG("api key not found: " + credential); + continue; + } + + auto credential_to_name_iter = rule.credential_to_name.find(credential); + if (credential_to_name_iter != rule.credential_to_name.end()) { + if (allow_set && !allow_set->empty()) { + if (allow_set->find(credential_to_name_iter->second) == + allow_set->end()) { + deniedUnauthorizedConsumer(rule.realm); + LOG_DEBUG("unauthorized consumer: " + + credential_to_name_iter->second); + return false; + } + } + addRequestHeader("X-Mse-Consumer", credential_to_name_iter->second); + } + return true; + } + } else { + for (const auto& consumer : rule.consumers) { + std::vector keys_to_check = + consumer.keys.value_or(rule.keys); + bool in_query = consumer.in_query.value_or(rule.in_query); + bool in_header = consumer.in_header.value_or(rule.in_header); + + for (const auto& key : keys_to_check) { + auto credential = extractCredential(in_header, in_query, key); + if (credential.empty()) { + LOG_DEBUG("empty credential for key: " + key); + continue; + } + + if (credential != consumer.credential) { + LOG_DEBUG("credential does not match the consumer's credential: " + + credential); + continue; + } + + auto auth_credential_iter = rule.credentials.find(credential); + if (auth_credential_iter == rule.credentials.end()) { + LOG_DEBUG("api key not found: " + credential); + continue; + } + + auto credential_to_name_iter = rule.credential_to_name.find(credential); + if (credential_to_name_iter != rule.credential_to_name.end()) { + if (allow_set && !allow_set->empty()) { + if (allow_set->find(credential_to_name_iter->second) == + allow_set->end()) { + deniedUnauthorizedConsumer(rule.realm); + LOG_DEBUG("unauthorized consumer: " + + credential_to_name_iter->second); + return false; + } + } + addRequestHeader("X-Mse-Consumer", credential_to_name_iter->second); + } + return true; + } + } } - auto auth_credential_iter = rule.credentials.find(std::string(credential)); - // Check if the credential is part of the credentials - // set from our container to grant or deny access. - if (auth_credential_iter == rule.credentials.end()) { - LOG_DEBUG(absl::StrCat("api key not found: ", credential)); - deniedInvalidCredentials(rule.realm); - return false; + + LOG_DEBUG("No valid credentials were found after checking all consumers."); + deniedInvalidCredentials(rule.realm); + return false; +} + +std::string PluginRootContext::extractCredential(bool in_header, bool in_query, + const std::string& key) { + if (in_header) { + auto header = getRequestHeader(key); + if (header->size() != 0) { + return header->toString(); + } } - // Check if this credential has a consumer name. If so, check if this - // consumer is allowed to access. If allow_set is empty, allow all consumers. - auto credential_to_name_iter = - rule.credential_to_name.find(std::string(std::string(credential))); - if (credential_to_name_iter != rule.credential_to_name.end()) { - if (allow_set && !allow_set.value().empty()) { - if (allow_set.value().find(credential_to_name_iter->second) == - allow_set.value().end()) { - deniedUnauthorizedConsumer(rule.realm); - LOG_DEBUG(credential_to_name_iter->second); - return false; - } + if (in_query) { + auto request_path_header = getRequestHeader(":path"); + auto path = request_path_header->view(); + auto params = Wasm::Common::Http::parseAndDecodeQueryString(path); + auto it = params.find(key); + if (it != params.end()) { + return it->second; } - addRequestHeader("X-Mse-Consumer", credential_to_name_iter->second); } - return true; + return ""; } bool PluginRootContext::onConfigure(size_t size) { @@ -234,31 +345,6 @@ bool PluginRootContext::configure(size_t configuration_size) { return true; } -std::string PluginRootContext::extractCredential( - const KeyAuthConfigRule& rule) { - auto request_path_header = getRequestHeader(":path"); - auto path = request_path_header->view(); - LOG_DEBUG(std::string(path)); - if (rule.in_query) { - auto params = Wasm::Common::Http::parseAndDecodeQueryString(path); - for (const auto& key : rule.keys) { - auto it = params.find(key); - if (it != params.end()) { - return it->second; - } - } - } - if (rule.in_header) { - for (const auto& key : rule.keys) { - auto header = getRequestHeader(key); - if (header->size() != 0) { - return header->toString(); - } - } - } - return ""; -} - FilterHeadersStatus PluginContext::onRequestHeaders(uint32_t, bool) { auto* rootCtx = rootContext(); return rootCtx->checkAuthRule( diff --git a/plugins/wasm-cpp/extensions/key_auth/plugin.h b/plugins/wasm-cpp/extensions/key_auth/plugin.h index 4a2b7418d3..4cf329d90c 100644 --- a/plugins/wasm-cpp/extensions/key_auth/plugin.h +++ b/plugins/wasm-cpp/extensions/key_auth/plugin.h @@ -36,7 +36,16 @@ namespace key_auth { #endif +struct Consumer { + std::string name; + std::string credential; + std::optional> keys; + std::optional in_query = std::nullopt; + std::optional in_header = std::nullopt; +}; + struct KeyAuthConfigRule { + std::vector consumers; std::unordered_set credentials; std::unordered_map credential_to_name; std::string realm = "MSE Gateway"; @@ -61,7 +70,8 @@ class PluginRootContext : public RootContext, private: bool parsePluginConfig(const json&, KeyAuthConfigRule&) override; - std::string extractCredential(const KeyAuthConfigRule&); + std::string extractCredential(bool in_header, bool in_query, + const std::string& key); }; // Per-stream context. diff --git a/plugins/wasm-cpp/extensions/key_auth/plugin_test.cc b/plugins/wasm-cpp/extensions/key_auth/plugin_test.cc index fc70df1b00..2f60811e00 100644 --- a/plugins/wasm-cpp/extensions/key_auth/plugin_test.cc +++ b/plugins/wasm-cpp/extensions/key_auth/plugin_test.cc @@ -121,7 +121,7 @@ TEST_F(KeyAuthTest, InQuery) { "_rules_": [ { "_match_route_": ["test"], - "credentials":["abc"], + "credentials":["abc","def"], "keys": ["apiKey", "x-api-key"] } ] @@ -144,6 +144,10 @@ TEST_F(KeyAuthTest, InQuery) { path_ = "/test?hello=123&apiKey=123"; EXPECT_EQ(context_->onRequestHeaders(0, false), FilterHeadersStatus::StopIteration); + + path_ = "/test?hello=123&apiKey=123&x-api-key=def"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::Continue); } TEST_F(KeyAuthTest, InQueryWithConsumer) { @@ -173,6 +177,29 @@ TEST_F(KeyAuthTest, InQueryWithConsumer) { FilterHeadersStatus::StopIteration); } +TEST_F(KeyAuthTest, EmptyConsumer) { + std::string configuration = R"( +{ + "consumers" : [], + "keys" : [ "apiKey", "x-api-key" ], + "_rules_" : [ {"_match_route_" : ["test"], "allow" : []} ] +})"; + BufferBase buffer; + buffer.set(configuration); + EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration)) + .WillOnce([&buffer](WasmBufferType) { return &buffer; }); + EXPECT_TRUE(root_context_->configure(configuration.size())); + + route_name_ = "test"; + path_ = "/test?hello=1&apiKey=abc"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + + route_name_ = "test2"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::Continue); +} + TEST_F(KeyAuthTest, InHeader) { std::string configuration = R"( { @@ -240,6 +267,40 @@ TEST_F(KeyAuthTest, InHeaderWithConsumer) { FilterHeadersStatus::StopIteration); } +TEST_F(KeyAuthTest, ConsumerDifferentKey) { + std::string configuration = R"( +{ + "consumers" : [ {"credential" : "abc", "name" : "consumer1", "keys" : [ "apiKey" ]}, {"credential" : "123", "name" : "consumer2"} ], + "keys" : [ "apiKey2" ], + "_rules_" : [ {"_match_route_" : ["test"], "allow" : ["consumer1"]}, {"_match_route_" : ["test2"], "allow" : ["consumer2"]} ] +})"; + BufferBase buffer; + buffer.set(configuration); + EXPECT_CALL(*mock_context_, getBuffer(WasmBufferType::PluginConfiguration)) + .WillOnce([&buffer](WasmBufferType) { return &buffer; }); + EXPECT_TRUE(root_context_->configure(configuration.size())); + + route_name_ = "test"; + path_ = "/test?hello=1&apiKey=abc"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::Continue); + + route_name_ = "test"; + path_ = "/test?hello=1&apiKey2=abc"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + + route_name_ = "test"; + path_ = "/test?hello=123&apiKey2=123"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + + route_name_ = "test2"; + path_ = "/test?hello=123&apiKey2=123"; + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::Continue); +} + } // namespace key_auth } // namespace null_plugin } // namespace proxy_wasm From e298078065db6550f8a6035c93676de139c75777 Mon Sep 17 00:00:00 2001 From: Jun <108045855+2456868764@users.noreply.github.com> Date: Wed, 16 Oct 2024 10:21:03 +0800 Subject: [PATCH 06/64] add dns&static registry e2e test (#1393) --- test/e2e/conformance/base/manifests.yaml | 52 +++++++++++++++++ .../tests/httproute-dns-registry.go | 57 +++++++++++++++++++ .../tests/httproute-dns-registry.yaml | 48 ++++++++++++++++ .../tests/httproute-static-registry.go | 57 +++++++++++++++++++ .../tests/httproute-static-registry.yaml | 48 ++++++++++++++++ 5 files changed, 262 insertions(+) create mode 100644 test/e2e/conformance/tests/httproute-dns-registry.go create mode 100644 test/e2e/conformance/tests/httproute-dns-registry.yaml create mode 100644 test/e2e/conformance/tests/httproute-static-registry.go create mode 100644 test/e2e/conformance/tests/httproute-static-registry.yaml diff --git a/test/e2e/conformance/base/manifests.yaml b/test/e2e/conformance/base/manifests.yaml index 235dce14dc..a8afe3b11c 100644 --- a/test/e2e/conformance/base/manifests.yaml +++ b/test/e2e/conformance/base/manifests.yaml @@ -80,6 +80,58 @@ spec: --- apiVersion: v1 kind: Service +metadata: + name: infra-backend-v1-ip + namespace: higress-conformance-infra +spec: + selector: + app: infra-backend-v1-ip + ports: + - protocol: TCP + port: 8080 + targetPort: 3000 + clusterIP: 10.96.254.254 + type: ClusterIP +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: infra-backend-v1-ip + namespace: higress-conformance-infra + labels: + app: infra-backend-v1-ip +spec: + replicas: 1 + selector: + matchLabels: + app: infra-backend-v1-ip + template: + metadata: + labels: + app: infra-backend-v1-ip + spec: + containers: + - name: infra-backend-v1-ip + # From https://github.com/kubernetes-sigs/ingress-controller-conformance/tree/master/images/echoserver + # image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echoserver:v20221109-7ee2f3e + + # From https://github.com/Uncle-Justice/echo-server + image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server:1.3.0 + env: + - name: POD_NAME + valueFrom: + fieldRef: + fieldPath: metadata.name + - name: NAMESPACE + valueFrom: + fieldRef: + fieldPath: metadata.namespace + resources: + requests: + cpu: 10m +--- +apiVersion: v1 +kind: Service metadata: name: infra-backend-v2 namespace: higress-conformance-infra diff --git a/test/e2e/conformance/tests/httproute-dns-registry.go b/test/e2e/conformance/tests/httproute-dns-registry.go new file mode 100644 index 0000000000..1864b8d394 --- /dev/null +++ b/test/e2e/conformance/tests/httproute-dns-registry.go @@ -0,0 +1,57 @@ +// 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(HTTPRouteDNSRegistry) +} + +var HTTPRouteDNSRegistry = suite.ConformanceTest{ + ShortName: "HTTPRouteDNSRegistry", + Description: "The Ingress in the higress-conformance-infra namespace uses the dns service registry.", + Manifests: []string{"tests/httproute-dns-registry.yaml"}, + Features: []suite.SupportedFeature{suite.HTTPConformanceFeature}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + testcases := []http.Assertion{ + { + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo.com", + Path: "/", + Method: "GET", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponseNoRequest: true, + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + } + t.Run("HTTPRoute DNS Registry", func(t *testing.T) { + for _, testcase := range testcases { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase) + } + }) + }, +} diff --git a/test/e2e/conformance/tests/httproute-dns-registry.yaml b/test/e2e/conformance/tests/httproute-dns-registry.yaml new file mode 100644 index 0000000000..8f992ac9ea --- /dev/null +++ b/test/e2e/conformance/tests/httproute-dns-registry.yaml @@ -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.higress.io/v1 +kind: McpBridge +metadata: + name: default + namespace: higress-system +spec: + registries: + - type: dns + domain: infra-backend-v1.higress-conformance-infra.svc.cluster.local + name: infra-backend-v1 + port: 8080 + protocol: http + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + higress.io/destination: infra-backend-v1.dns + name: httproute-infra-backend-v1-dns-ingress + namespace: higress-system +spec: + ingressClassName: higress + rules: + - host: "foo.com" + http: + paths: + - pathType: Prefix + path: / + backend: + resource: + apiGroup: networking.higress.io + kind: McpBridge + name: default diff --git a/test/e2e/conformance/tests/httproute-static-registry.go b/test/e2e/conformance/tests/httproute-static-registry.go new file mode 100644 index 0000000000..b95acf4a6c --- /dev/null +++ b/test/e2e/conformance/tests/httproute-static-registry.go @@ -0,0 +1,57 @@ +// 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(HTTPRouteStaticRegistry) +} + +var HTTPRouteStaticRegistry = suite.ConformanceTest{ + ShortName: "HTTPRouteStaticRegistry", + Description: "The Ingress in the higress-conformance-infra namespace uses the static service registry.", + Manifests: []string{"tests/httproute-static-registry.yaml"}, + Features: []suite.SupportedFeature{suite.HTTPConformanceFeature}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + testcases := []http.Assertion{ + { + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "foo.com", + Path: "/", + Method: "GET", + }, + }, + Response: http.AssertionResponse{ + ExpectedResponseNoRequest: true, + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + } + t.Run("HTTPRoute Static Registry", func(t *testing.T) { + for _, testcase := range testcases { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase) + } + }) + }, +} diff --git a/test/e2e/conformance/tests/httproute-static-registry.yaml b/test/e2e/conformance/tests/httproute-static-registry.yaml new file mode 100644 index 0000000000..310302fbb8 --- /dev/null +++ b/test/e2e/conformance/tests/httproute-static-registry.yaml @@ -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.higress.io/v1 +kind: McpBridge +metadata: + name: default + namespace: higress-system +spec: + registries: + - type: static + domain: 10.96.254.254:8080 + name: infra-backend-v1-ip + port: 8080 + protocol: http + +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + higress.io/destination: infra-backend-v1-ip.static + name: httproute-infra-backend-v1-ip-ingress + namespace: higress-system +spec: + ingressClassName: higress + rules: + - host: "foo.com" + http: + paths: + - pathType: Prefix + path: / + backend: + resource: + apiGroup: networking.higress.io + kind: McpBridge + name: default From d0693d8c4bab0b429d6b58e90589b64bceaac042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Wed, 16 Oct 2024 11:17:44 +0800 Subject: [PATCH 07/64] Update SECURITY.md --- SECURITY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/SECURITY.md b/SECURITY.md index e4f906b953..d643ca19b9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,6 +4,7 @@ | Version | Supported | | ------- | ------------------ | +| 2.x.x | :white_check_mark: | | 1.x.x | :white_check_mark: | | < 1.0.0 | :x: | From 51c956f0b3f5f0bdc8823295d11e5920d08d3caa Mon Sep 17 00:00:00 2001 From: Kent Dong Date: Wed, 16 Oct 2024 18:42:49 +0800 Subject: [PATCH 08/64] fix: Fix clean targets in Makefile (#1397) --- Makefile.core.mk | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile.core.mk b/Makefile.core.mk index 95341d0f5f..55a9662f70 100644 --- a/Makefile.core.mk +++ b/Makefile.core.mk @@ -221,11 +221,15 @@ clean-higress: ## Cleans all the intermediate files and folders previously gener rm -rf $(DIRS_TO_CLEAN) clean-istio: + rm -rf external/api + rm -rf external/client-go rm -rf external/istio + rm -rf external/pkg clean-gateway: clean-istio rm -rf external/envoy rm -rf external/proxy + rm -rf external/go-control-plane rm -rf external/package/envoy.tar.gz clean-env: From 6f86c31bac81b7604d4a45854a5b811bfa44a171 Mon Sep 17 00:00:00 2001 From: Kent Dong Date: Wed, 16 Oct 2024 19:00:18 +0800 Subject: [PATCH 09/64] feat: Update submodules: envoy/envoy, istio/isitio (#1398) --- envoy/envoy | 2 +- istio/istio | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/envoy/envoy b/envoy/envoy index b3541845c1..e9302f5574 160000 --- a/envoy/envoy +++ b/envoy/envoy @@ -1 +1 @@ -Subproject commit b3541845c1a78d817c73806299415439c23488d2 +Subproject commit e9302f55742b53b617984cf25872c193357b6159 diff --git a/istio/istio b/istio/istio index d380470e53..1dbd773596 160000 --- a/istio/istio +++ b/istio/istio @@ -1 +1 @@ -Subproject commit d380470e53b6aa45b7a8ab2bf26cbc6c147da06f +Subproject commit 1dbd77359624ab4af2953a4840927fbd7ea1d668 From e923cbaeccab033efff42f53c51248e547dfefee Mon Sep 17 00:00:00 2001 From: Smoothengineer <160827599+Smoothengineer@users.noreply.github.com> Date: Thu, 17 Oct 2024 10:23:04 +0530 Subject: [PATCH 10/64] Update README_EN.md (#1402) --- README_EN.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README_EN.md b/README_EN.md index 4413ab8c0c..8ce33aa069 100644 --- a/README_EN.md +++ b/README_EN.md @@ -47,7 +47,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co Higress can function as a microservice gateway, which can discovery microservices from various service registries, such as Nacos, ZooKeeper, Consul, Eureka, etc. - It deeply integrates of [Dubbo](https://github.com/apache/dubbo), [Nacos](https://github.com/alibaba/nacos), [Sentinel](https://github.com/alibaba/Sentinel) and other microservice technology stacks. + It deeply integrates with [Dubbo](https://github.com/apache/dubbo), [Nacos](https://github.com/alibaba/nacos), [Sentinel](https://github.com/alibaba/Sentinel) and other microservice technology stacks. - **Security gateway**: @@ -57,7 +57,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co - **Easy to use** - Provide one-stop gateway solutions for traffic scheduling, service management, and security protection, support Console, K8s Ingress, and Gateway API configuration methods, and also support HTTP to Dubbo protocol conversion, and easily complete protocol mapping configuration. + Provides one-stop gateway solutions for traffic scheduling, service management, and security protection, support Console, K8s Ingress, and Gateway API configuration methods, and also support HTTP to Dubbo protocol conversion, and easily complete protocol mapping configuration. - **Easy to expand** @@ -73,7 +73,7 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co - **Security** - Provides JWT, OIDC, custom authentication and authentication, deeply integrates open source web application firewall. + Provides JWT, OIDC, custom authentication and authentication, deeply integrates open-source web application firewall. ## Community @@ -81,9 +81,9 @@ Powered by [Istio](https://github.com/istio/istio) and [Envoy](https://github.co ### Thanks -Higress would not be possible without the valuable open-source work of projects in the community. We would like to extend a special thank-you to Envoy and Istio. +Higress would not be possible without the valuable open-source work of projects in the community. We would like to extend a special thank you to Envoy and Istio. ### Related Repositories - Higress Console: https://github.com/higress-group/higress-console -- Higress Standalone: https://github.com/higress-group/higress-standalone \ No newline at end of file +- Higress Standalone: https://github.com/higress-group/higress-standalone From 7e6168a644c0c9c625fbe29331d83e00a48da2fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 17 Oct 2024 14:31:26 +0800 Subject: [PATCH 11/64] Update README.md --- README.md | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 834b787a5d..5ef491b5f3 100644 --- a/README.md +++ b/README.md @@ -21,14 +21,15 @@

-Higress 是基于阿里内部多年的 Envoy Gateway 实践沉淀,以开源 [Istio](https://github.com/istio/istio) 与 [Envoy](https://github.com/envoyproxy/envoy) 为核心构建的云原生 API 网关。 +Higress 是一款云原生 API 网关,内核基于 Istio 和 Envoy,可以用 Go/Rust/JS 等编写 Wasm 插件,提供了数十个现成的通用插件,以及开箱即用的控制台(demo 点[这里](http://demo.higress.io/)) -Higress 在阿里内部作为 AI 网关,承载了通义千问 APP、百炼大模型 API、机器学习 PAI 平台等 AI 业务的流量。 +Higress 在阿里内部为解决 Tengine reload 对长连接业务有损,以及 gRPC/Dubbo 负载均衡能力不足而诞生。 -Higress 能够用统一的协议对接国内外所有 LLM 模型厂商,同时具备丰富的 AI 可观测、多模型负载均衡/fallback、AI token 流控、AI 缓存等能力: +阿里云基于 Higress 构建了云原生 API 网关产品,为大量企业客户提供 99.99% 的网关高可用保障服务能力。 -![](https://img.alicdn.com/imgextra/i1/O1CN01fNnhCp1cV8mYPRFeS_!!6000000003605-0-tps-1080-608.jpg) +Higress 基于 AI 网关能力,支撑了通义千问 APP、百炼大模型 API、机器学习 PAI 平台等 AI 业务。同时服务国内头部的 AIGC 企业(如零一万物),以及 AI 产品(如 FastGPT) +![](https://img.alicdn.com/imgextra/i2/O1CN011AbR8023V8R5N0HcA_!!6000000007260-2-tps-1080-606.png) ## Summary @@ -67,23 +68,32 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start - **AI 网关**: - Higress 提供了一站式的 AI 插件集,可以增强依赖 AI 能力业务的稳定性、灵活性、可观测性,使得业务与 AI 的集成更加便捷和高效。 + Higress 能够用统一的协议对接国内外所有 LLM 模型厂商,同时具备丰富的 AI 可观测、多模型负载均衡/fallback、AI token 流控、AI 缓存等能力: + + ![](https://img.alicdn.com/imgextra/i1/O1CN01fNnhCp1cV8mYPRFeS_!!6000000003605-0-tps-1080-608.jpg) - **Kubernetes Ingress 网关**: Higress 可以作为 K8s 集群的 Ingress 入口网关, 并且兼容了大量 K8s Nginx Ingress 的注解,可以从 K8s Nginx Ingress 快速平滑迁移到 Higress。 支持 [Gateway API](https://gateway-api.sigs.k8s.io/) 标准,支持用户从 Ingress API 平滑迁移到 Gateway API。 + + 相比 ingress-nginx,资源开销大幅下降,路由变更生效速度有十倍提升: + + ![](https://img.alicdn.com/imgextra/i1/O1CN01bhEtb229eeMNBWmdP_!!6000000008093-2-tps-750-547.png) + ![](https://img.alicdn.com/imgextra/i1/O1CN01bqRets1LsBGyitj4S_!!6000000001354-2-tps-887-489.png) - **微服务网关**: Higress 可以作为微服务网关, 能够对接多种类型的注册中心发现服务配置路由,例如 Nacos, ZooKeeper, Consul, Eureka 等。 并且深度集成了 [Dubbo](https://github.com/apache/dubbo), [Nacos](https://github.com/alibaba/nacos), [Sentinel](https://github.com/alibaba/Sentinel) 等微服务技术栈,基于 Envoy C++ 网关内核的出色性能,相比传统 Java 类微服务网关,可以显著降低资源使用率,减少成本。 + + ![](https://img.alicdn.com/imgextra/i4/O1CN01v4ZbCj1dBjePSMZ17_!!6000000003698-0-tps-1613-926.jpg) - **安全防护网关**: - Higress 可以作为安全防护网关, 提供 WAF 的能力,并且支持多种认证鉴权策略,例如 key-auth, hmac-auth, jwt-auth, basic-auth, oidc 等。 + Higress 可以作为安全防护网关, 提供 WAF 的能力,并且支持多种认证鉴权策略,例如 key-auth, hmac-auth, jwt-auth, basic-auth, oidc 等。 ## 核心优势 @@ -176,4 +186,4 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start ### 关联仓库 - Higress 控制台:https://github.com/higress-group/higress-console -- Higress(独立运行版):https://github.com/higress-group/higress-standalone \ No newline at end of file +- Higress(独立运行版):https://github.com/higress-group/higress-standalone From 299621476f61389f2819ffe0df260d7ab28c0c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 17 Oct 2024 14:33:08 +0800 Subject: [PATCH 12/64] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5ef491b5f3..6495691fe4 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) [**官网**](https://higress.cn/)   | -  [**文档**](https://higress.cn/docs/latest/user/quickstart/)   | +  [**文档**](https://higress.cn/docs/latest/overview/what-is-higress/)   |   [**博客**](https://higress.cn/blog/)   |   [**电子书**](https://higress.cn/docs/ebook/wasm14/)   |   [**开发指引**](https://higress.cn/docs/latest/dev/architecture/)   | From c67f494b497fc074a7eca398e9f8a7205f02c1d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Fri, 18 Oct 2024 09:54:10 +0800 Subject: [PATCH 13/64] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6495691fe4..87468ed9ad 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start ### 交流群 -![image](https://img.alicdn.com/imgextra/i2/O1CN01qPd7Ix1uZPVEsWjWp_!!6000000006051-0-tps-720-405.jpg) +![image](https://img.alicdn.com/imgextra/i2/O1CN01BkopaB22ZsvamFftE_!!6000000007135-0-tps-720-405.jpg) ### 技术分享 From 11ff2d1d31594a73d5b870de039ff52a066ad58d Mon Sep 17 00:00:00 2001 From: mamba <371510756@qq.com> Date: Fri, 18 Oct 2024 13:58:52 +0800 Subject: [PATCH 14/64] [frontend-gray] support grayKey from localStorage (#1395) --- .../extensions/frontend-gray/README.md | 25 ++++++++++ .../extensions/frontend-gray/config/config.go | 27 ++++++---- .../extensions/frontend-gray/envoy.yaml | 16 +++--- .../wasm-go/extensions/frontend-gray/main.go | 50 +++++++++++-------- .../extensions/frontend-gray/util/utils.go | 16 +++++- .../frontend-gray/util/utils_test.go | 24 +++++++++ 6 files changed, 115 insertions(+), 43 deletions(-) diff --git a/plugins/wasm-go/extensions/frontend-gray/README.md b/plugins/wasm-go/extensions/frontend-gray/README.md index 8dee32008b..dba4b73c0f 100644 --- a/plugins/wasm-go/extensions/frontend-gray/README.md +++ b/plugins/wasm-go/extensions/frontend-gray/README.md @@ -17,6 +17,7 @@ description: 前端灰度插件配置参考 | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | |----------------|--------------|----|-----|----------------------------------------------------------------------------------------------------| | `grayKey` | string | 非必填 | - | 用户ID的唯一标识,可以来自Cookie或者Header中,比如 userid,如果没有填写则使用`rules[].grayTagKey`和`rules[].grayTagValue`过滤灰度规则 | +| `localStorageGrayKey` | string | 非必填 | - | 使用JWT鉴权方式,用户ID的唯一标识来自`localStorage`中,如果配置了当前参数,则`grayKey`失效 | | `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` | | `userStickyMaxAge` | int | 非必填 | 172800 | 用户粘滞的时长:单位为秒,默认为`172800`,2天时间 | | `rules` | array of object | 必填 | - | 用户定义不同的灰度规则,适配不同的灰度场景 | @@ -168,6 +169,30 @@ cookie存在`appInfo`的JSON数据,其中包含`userId`字段为当前的唯 否则使用`version: base`版本 +### 用户信息存储在LocalStorage +由于网关插件需要识别用户为唯一身份信息,HTTP协议进行信息传输,只能在Header中传递。如果用户信息存储在LocalStorage,在首页注入一段脚本将LocalStorage中的用户信息设置到cookie中。 +``` +(function() { + var grayKey = '@@X_GRAY_KEY'; + var cookies = document.cookie.split('; ').filter(function(row) { + return row.indexOf(grayKey + '=') === 0; + }); + + try { + if (typeof localStorage !== 'undefined' && localStorage !== null) { + var storageValue = localStorage.getItem(grayKey); + var cookieValue = cookies.length > 0 ? decodeURIComponent(cookies[0].split('=')[1]) : null; + if (storageValue && storageValue.indexOf('=') < 0 && cookieValue && cookieValue !== storageValue) { + document.cookie = grayKey + '=' + encodeURIComponent(storageValue) + '; path=/;'; + window.location.reload(); + } + } + } catch (error) { + // xx + } +})(); +``` + ### rewrite重写配置 > 一般用于CDN部署场景 ```yml diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config.go b/plugins/wasm-go/extensions/frontend-gray/config/config.go index de689aad2a..ecfbb3a836 100644 --- a/plugins/wasm-go/extensions/frontend-gray/config/config.go +++ b/plugins/wasm-go/extensions/frontend-gray/config/config.go @@ -49,17 +49,18 @@ type BodyInjection struct { } type GrayConfig struct { - UserStickyMaxAge string - TotalGrayWeight int - GrayKey string - GraySubKey string - Rules []*GrayRule - Rewrite *Rewrite - Html string - BaseDeployment *Deployment - GrayDeployments []*Deployment - BackendGrayTag string - Injection *Injection + UserStickyMaxAge string + TotalGrayWeight int + GrayKey string + LocalStorageGrayKey string + GraySubKey string + Rules []*GrayRule + Rewrite *Rewrite + Html string + BaseDeployment *Deployment + GrayDeployments []*Deployment + BackendGrayTag string + Injection *Injection } func convertToStringList(results []gjson.Result) []string { @@ -81,7 +82,11 @@ func convertToStringMap(result gjson.Result) map[string]string { func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { // 解析 GrayKey + grayConfig.LocalStorageGrayKey = json.Get("localStorageGrayKey").String() grayConfig.GrayKey = json.Get("grayKey").String() + if grayConfig.LocalStorageGrayKey != "" { + grayConfig.GrayKey = grayConfig.LocalStorageGrayKey + } grayConfig.GraySubKey = json.Get("graySubKey").String() grayConfig.BackendGrayTag = json.Get("backendGrayTag").String() grayConfig.UserStickyMaxAge = json.Get("userStickyMaxAge").String() diff --git a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml index 6dabed21de..239e221bd7 100644 --- a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml +++ b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml @@ -73,23 +73,22 @@ static_resources: ], "rewrite": { "host": "frontend-gray-cn-shanghai.oss-cn-shanghai-internal.aliyuncs.com", - "notFoundUri": "/cygtapi/{version}/333.html", "indexRouting": { - "/app1": "/cygtapi/{version}/index.html", - "/": "/cygtapi/{version}/index.html" + "/app1": "/mfe/app1/{version}/index.html", + "/": "/mfe/app1/{version}/index.html" }, "fileRouting": { - "/": "/cygtapi/{version}", - "/app1": "/cygtapi/{version}" + "/": "/mfe/app1/{version}", + "/app1": "/mfe/app1/{version}" } }, "baseDeployment": { - "version": "base" + "version": "dev" }, "grayDeployments": [ { "name": "beta-user", - "version": "gray", + "version": "0.0.1", "enabled": true } ], @@ -107,8 +106,7 @@ static_resources: "" ] } - }, - "html": "\n \n\napp1\n\n\n\n\t测试替换html版本\n\t
\n\t版本: {version}\n\t
\n\t\n\n" + } } - name: envoy.filters.http.router typed_config: diff --git a/plugins/wasm-go/extensions/frontend-gray/main.go b/plugins/wasm-go/extensions/frontend-gray/main.go index 81eb3034da..b1b5d28aff 100644 --- a/plugins/wasm-go/extensions/frontend-gray/main.go +++ b/plugins/wasm-go/extensions/frontend-gray/main.go @@ -21,14 +21,13 @@ func main() { wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), wrapper.ProcessResponseHeadersBy(onHttpResponseHeader), wrapper.ProcessResponseBodyBy(onHttpResponseBody), - wrapper.ProcessStreamingResponseBodyBy(onStreamingResponseBody), ) } func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.Log) error { // 解析json 为GrayConfig config.JsonToGrayConfig(json, grayConfig) - log.Infof("Rewrite: %v, GrayDeployments: %v", json.Get("rewrite"), json.Get("grayDeployments")) + log.Debugf("Rewrite: %v, GrayDeployments: %v", json.Get("rewrite"), json.Get("grayDeployments")) return nil } @@ -98,15 +97,17 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } else { rewritePath = util.PrefixFileRewrite(path, deployment.Version, grayConfig.Rewrite.File) } - log.Infof("rewrite path: %s %s %v", path, deployment.Version, rewritePath) - proxywasm.ReplaceHttpRequestHeader(":path", rewritePath) + if path != rewritePath { + log.Infof("rewrite path:%s, rewritePath:%s, Version:%v", path, rewritePath, deployment.Version) + proxywasm.ReplaceHttpRequestHeader(":path", rewritePath) + } } - return types.ActionContinue } func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log wrapper.Log) types.Action { if !util.IsGrayEnabled(grayConfig) { + ctx.DontReadResponseBody() return types.ActionContinue } isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool) @@ -117,6 +118,9 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, if !isPageRequest { ctx.DontReadResponseBody() return types.ActionContinue + } else { + // 不会进去Streaming 的Body处理 + ctx.BufferResponseBody() } status, err := proxywasm.GetHttpResponseHeader(":status") @@ -159,8 +163,6 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, return types.ActionContinue } - // 不会进去Streaming 的Body处理 - ctx.BufferResponseBody() proxywasm.ReplaceHttpResponseHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) @@ -184,6 +186,11 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b if !ok { isPageRequest = false // 默认值 } + // 只处理首页相关请求 + if !isPageRequest { + return types.ActionContinue + } + frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool) if !ok { @@ -212,7 +219,8 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b return types.ActionContinue } - if isPageRequest && isNotFound && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" { + // 针对404页面处理 + if isNotFound && grayConfig.Rewrite.Host != "" && grayConfig.Rewrite.NotFound != "" { client := wrapper.NewClusterClient(wrapper.RouteCluster{Host: grayConfig.Rewrite.Host}) client.Get(strings.Replace(grayConfig.Rewrite.NotFound, "{version}", frontendVersion, -1), nil, func(statusCode int, responseHeaders http.Header, responseBody []byte) { @@ -222,20 +230,18 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b return types.ActionPause } - if isPageRequest { - // 将原始字节转换为字符串 - newBody := string(body) - - newBody = util.InjectContent(newBody, grayConfig.Injection) - - if err := proxywasm.ReplaceHttpResponseBody([]byte(newBody)); err != nil { - return types.ActionContinue - } + // 处理响应体HTML + newBody := string(body) + newBody = util.InjectContent(newBody, grayConfig.Injection) + if grayConfig.LocalStorageGrayKey != "" { + localStr := strings.ReplaceAll(` + `, "@@X_GRAY_KEY", grayConfig.LocalStorageGrayKey) + newBody = strings.ReplaceAll(newBody, "", "\n"+localStr) + } + if err := proxywasm.ReplaceHttpResponseBody([]byte(newBody)); err != nil { + return types.ActionContinue } - return types.ActionContinue } - -func onStreamingResponseBody(ctx wrapper.HttpContext, pluginConfig config.GrayConfig, chunk []byte, isLastChunk bool, log wrapper.Log) []byte { - return chunk -} diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils.go b/plugins/wasm-go/extensions/frontend-gray/util/utils.go index 80291a2c3c..e67d3e0893 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils.go @@ -142,8 +142,22 @@ func IsPageRequest(fetchMode string, myPath string) bool { // 首页Rewrite func IndexRewrite(path, version string, matchRules map[string]string) string { - for prefix, rewrite := range matchRules { + // Create a slice of keys in matchRules and sort them by length in descending order + keys := make([]string, 0, len(matchRules)) + for prefix := range matchRules { + keys = append(keys, prefix) + } + sort.Slice(keys, func(i, j int) bool { + if len(keys[i]) != len(keys[j]) { + return len(keys[i]) > len(keys[j]) // Sort by length + } + return keys[i] < keys[j] // Sort lexicographically + }) + + // Iterate over sorted keys to find the longest match + for _, prefix := range keys { if strings.HasPrefix(path, prefix) { + rewrite := matchRules[prefix] newPath := strings.Replace(rewrite, "{version}", version, -1) return newPath } diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go index d51e17f323..b6681c98af 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go @@ -53,6 +53,30 @@ func TestIndexRewrite(t *testing.T) { } } +func TestIndexRewrite2(t *testing.T) { + matchRules := map[string]string{ + "/": "/{version}/index.html", + "/sta": "/sta/{version}/index.html", + "/static": "/static/{version}/index.html", + } + + var tests = []struct { + path, output string + }{ + {"/static123", "/static/v1.0.0/index.html"}, + {"/static", "/static/v1.0.0/index.html"}, + {"/sta", "/sta/v1.0.0/index.html"}, + {"/", "/v1.0.0/index.html"}, + } + for _, test := range tests { + testName := test.path + t.Run(testName, func(t *testing.T) { + output := IndexRewrite(testName, "v1.0.0", matchRules) + assert.Equal(t, test.output, output) + }) + } +} + func TestPrefixFileRewrite(t *testing.T) { matchRules := map[string]string{ // 前缀匹配 From 49bb5ec2b9f9ba628667a8a95d498e33f56e8459 Mon Sep 17 00:00:00 2001 From: Lisheng Zheng Date: Fri, 18 Oct 2024 15:34:34 +0800 Subject: [PATCH 15/64] fix: add HTTP2 protocol options to skywalking and otel cluster (#1379) --- Makefile.core.mk | 8 +- pkg/ingress/kube/configmap/tracing.go | 145 +++++++++++++++++--------- 2 files changed, 102 insertions(+), 51 deletions(-) diff --git a/Makefile.core.mk b/Makefile.core.mk index 55a9662f70..b790d3c878 100644 --- a/Makefile.core.mk +++ b/Makefile.core.mk @@ -72,17 +72,17 @@ go.test.coverage: prebuild .PHONY: build build: prebuild $(OUT) - GOPROXY=$(GOPROXY) GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(HIGRESS_BINARIES) + GOPROXY="$(GOPROXY)" GOOS=$(GOOS_LOCAL) GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT)/ $(HIGRESS_BINARIES) .PHONY: build-linux build-linux: prebuild $(OUT) - GOPROXY=$(GOPROXY) GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(HIGRESS_BINARIES) + GOPROXY="$(GOPROXY)" GOOS=linux GOARCH=$(GOARCH_LOCAL) LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh $(OUT_LINUX)/ $(HIGRESS_BINARIES) $(AMD64_OUT_LINUX)/higress: - GOPROXY=$(GOPROXY) GOOS=linux GOARCH=amd64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_amd64/ $(HIGRESS_BINARIES) + GOPROXY="$(GOPROXY)" GOOS=linux GOARCH=amd64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_amd64/ $(HIGRESS_BINARIES) $(ARM64_OUT_LINUX)/higress: - GOPROXY=$(GOPROXY) GOOS=linux GOARCH=arm64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_arm64/ $(HIGRESS_BINARIES) + GOPROXY="$(GOPROXY)" GOOS=linux GOARCH=arm64 LDFLAGS=$(RELEASE_LDFLAGS) tools/hack/gobuild.sh ./out/linux_arm64/ $(HIGRESS_BINARIES) .PHONY: build-hgctl build-hgctl: prebuild $(OUT) diff --git a/pkg/ingress/kube/configmap/tracing.go b/pkg/ingress/kube/configmap/tracing.go index 209c610d8f..0529ccf858 100644 --- a/pkg/ingress/kube/configmap/tracing.go +++ b/pkg/ingress/kube/configmap/tracing.go @@ -255,69 +255,120 @@ func (t *TracingController) ConstructEnvoyFilters() ([]*config.Config, error) { return configs, nil } - config := &config.Config{ - Meta: config.Meta{ - GroupVersionKind: gvk.EnvoyFilter, - Name: higressTracingEnvoyFilterName, - Namespace: namespace, - }, - Spec: &networking.EnvoyFilter{ - ConfigPatches: []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ - { - ApplyTo: networking.EnvoyFilter_NETWORK_FILTER, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_GATEWAY, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ - Listener: &networking.EnvoyFilter_ListenerMatch{ - FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ - Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ - Name: "envoy.filters.network.http_connection_manager", - }, - }, + configPatches := []*networking.EnvoyFilter_EnvoyConfigObjectPatch{ + { + ApplyTo: networking.EnvoyFilter_NETWORK_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: "envoy.filters.network.http_connection_manager", }, }, }, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_MERGE, - Value: util.BuildPatchStruct(tracingConfig), - }, }, - { - ApplyTo: networking.EnvoyFilter_HTTP_FILTER, - Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ - Context: networking.EnvoyFilter_GATEWAY, - ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ - Listener: &networking.EnvoyFilter_ListenerMatch{ - FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ - Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ - Name: "envoy.filters.network.http_connection_manager", - SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{ - Name: "envoy.filters.http.router", - }, - }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: util.BuildPatchStruct(tracingConfig), + }, + }, + { + ApplyTo: networking.EnvoyFilter_HTTP_FILTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Listener{ + Listener: &networking.EnvoyFilter_ListenerMatch{ + FilterChain: &networking.EnvoyFilter_ListenerMatch_FilterChainMatch{ + Filter: &networking.EnvoyFilter_ListenerMatch_FilterMatch{ + Name: "envoy.filters.network.http_connection_manager", + SubFilter: &networking.EnvoyFilter_ListenerMatch_SubFilterMatch{ + Name: "envoy.filters.http.router", }, }, }, }, - Patch: &networking.EnvoyFilter_Patch{ - Operation: networking.EnvoyFilter_Patch_MERGE, - Value: util.BuildPatchStruct(`{ + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: util.BuildPatchStruct(`{ "name":"envoy.filters.http.router", "typed_config":{ "@type": "type.googleapis.com/envoy.extensions.filters.http.router.v3.Router", "start_child_span": true } }`), - }, - }, }, }, } + patches := t.constructTracingExtendPatches(tracing) + configPatches = append(configPatches, patches...) + + config := &config.Config{ + Meta: config.Meta{ + GroupVersionKind: gvk.EnvoyFilter, + Name: higressTracingEnvoyFilterName, + Namespace: namespace, + }, + Spec: &networking.EnvoyFilter{ + ConfigPatches: configPatches, + }, + } + configs = append(configs, config) return configs, nil } +func tracingClusterName(port, service string) string { + return fmt.Sprintf("outbound|%s||%s", port, service) +} + +func (t *TracingController) constructHTTP2ProtocolOptionsPatch(port, service string) *networking.EnvoyFilter_EnvoyConfigObjectPatch { + http2ProtocolOptions := `{"typed_extension_protocol_options": { + "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": { + "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions", + "explicit_http_config": { + "http2_protocol_options": {} + } + } +}}` + + return &networking.EnvoyFilter_EnvoyConfigObjectPatch{ + ApplyTo: networking.EnvoyFilter_CLUSTER, + Match: &networking.EnvoyFilter_EnvoyConfigObjectMatch{ + Context: networking.EnvoyFilter_GATEWAY, + ObjectTypes: &networking.EnvoyFilter_EnvoyConfigObjectMatch_Cluster{ + Cluster: &networking.EnvoyFilter_ClusterMatch{ + Name: tracingClusterName(port, service), + }, + }, + }, + Patch: &networking.EnvoyFilter_Patch{ + Operation: networking.EnvoyFilter_Patch_MERGE, + Value: util.BuildPatchStruct(http2ProtocolOptions), + }, + } +} + +func (t *TracingController) constructTracingExtendPatches(tracing *Tracing) []*networking.EnvoyFilter_EnvoyConfigObjectPatch { + if tracing == nil { + return nil + } + var patches []*networking.EnvoyFilter_EnvoyConfigObjectPatch + if skywalking := tracing.Skywalking; skywalking != nil { + patches = append(patches, t.constructHTTP2ProtocolOptionsPatch(skywalking.Port, skywalking.Service)) + } + if otel := tracing.OpenTelemetry; otel != nil { + patches = append(patches, t.constructHTTP2ProtocolOptionsPatch(otel.Port, otel.Service)) + } + + return patches +} + func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace string) string { tracingConfig := "" timeout := float32(tracing.Timeout) / 1000 @@ -338,7 +389,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s }, "grpc_service": { "envoy_grpc": { - "cluster_name": "outbound|%s||%s" + "cluster_name": "%s" }, "timeout": "%.3fs" } @@ -349,7 +400,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s } } } -}`, namespace, skywalking.AccessToken, skywalking.Port, skywalking.Service, timeout, tracing.Sampling) +}`, namespace, skywalking.AccessToken, tracingClusterName(skywalking.Port, skywalking.Service), timeout, tracing.Sampling) } if tracing.Zipkin != nil { @@ -363,7 +414,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s "name": "envoy.tracers.zipkin", "typed_config": { "@type": "type.googleapis.com/envoy.config.trace.v3.ZipkinConfig", - "collector_cluster": "outbound|%s||%s", + "collector_cluster": "%s", "collector_endpoint": "/api/v2/spans", "collector_hostname": "higress-gateway", "collector_endpoint_version": "HTTP_JSON", @@ -375,7 +426,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s } } } -}`, zipkin.Port, zipkin.Service, tracing.Sampling) +}`, tracingClusterName(zipkin.Port, zipkin.Service), tracing.Sampling) } if tracing.OpenTelemetry != nil { @@ -392,7 +443,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s "service_name": "higress-gateway.%s", "grpc_service": { "envoy_grpc": { - "cluster_name": "outbound|%s||%s" + "cluster_name": "%s" }, "timeout": "%.3fs" } @@ -403,7 +454,7 @@ func (t *TracingController) constructTracingTracer(tracing *Tracing, namespace s } } } -}`, namespace, opentelemetry.Port, opentelemetry.Service, timeout, tracing.Sampling) +}`, namespace, tracingClusterName(opentelemetry.Port, opentelemetry.Service), timeout, tracing.Sampling) } return tracingConfig } From 32e5a59ae01e14c5f2accc823e5401c0bce2bb8e Mon Sep 17 00:00:00 2001 From: rinfx <893383980@qq.com> Date: Fri, 18 Oct 2024 16:32:48 +0800 Subject: [PATCH 16/64] fix special charactor handle in ai-security-guard plugin (#1394) --- .../extensions/ai-security-guard/main.go | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/plugins/wasm-go/extensions/ai-security-guard/main.go b/plugins/wasm-go/extensions/ai-security-guard/main.go index 3051fe22ea..ca59f7f6a1 100644 --- a/plugins/wasm-go/extensions/ai-security-guard/main.go +++ b/plugins/wasm-go/extensions/ai-security-guard/main.go @@ -81,7 +81,7 @@ func (config *AISecurityConfig) incrementCounter(metricName string, inc uint64) func urlEncoding(rawStr string) string { encodedStr := url.PathEscape(rawStr) - encodedStr = strings.ReplaceAll(encodedStr, "+", "%20") + encodedStr = strings.ReplaceAll(encodedStr, "+", "%2B") encodedStr = strings.ReplaceAll(encodedStr, ":", "%3A") encodedStr = strings.ReplaceAll(encodedStr, "=", "%3D") encodedStr = strings.ReplaceAll(encodedStr, "&", "%26") @@ -106,7 +106,7 @@ func getSign(params map[string]string, secret string) string { }) canonicalStr := strings.Join(paramArray, "&") signStr := "POST&%2F&" + urlEncoding(canonicalStr) - // proxywasm.LogInfo(signStr) + proxywasm.LogDebugf("String to sign is: %s", signStr) return hmacSha1(signStr, secret) } @@ -196,10 +196,11 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config AISecurityConfig, log func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body []byte, log wrapper.Log) types.Action { log.Debugf("checking request body...") - content := gjson.GetBytes(body, config.requestContentJsonPath).String() + content := gjson.GetBytes(body, config.requestContentJsonPath).Raw model := gjson.GetBytes(body, "model").Raw ctx.SetContext("requestModel", model) - if content != "" { + log.Debugf("Raw response content is: %s", content) + if len(content) > 0 { timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z") randomID, _ := generateHexID(16) params := map[string]string{ @@ -212,7 +213,7 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body [] "AccessKeyId": config.ak, "Timestamp": timestamp, "Service": config.requestCheckService, - "ServiceParameters": `{"content": "` + content + `"}`, + "ServiceParameters": fmt.Sprintf(`{"content": %s}`, content), } signature := getSign(params, config.sk+"&") reqParams := url.Values{} @@ -339,7 +340,7 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AISecurityConfig, body [ if isStreamingResponse { content = extractMessageFromStreamingBody(body, config.responseStreamContentJsonPath) } else { - content = gjson.GetBytes(body, config.responseContentJsonPath).String() + content = gjson.GetBytes(body, config.responseContentJsonPath).Raw } log.Debugf("Raw response content is: %s", content) if len(content) > 0 { @@ -355,7 +356,7 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AISecurityConfig, body [ "AccessKeyId": config.ak, "Timestamp": timestamp, "Service": config.responseCheckService, - "ServiceParameters": `{"content": "` + content + `"}`, + "ServiceParameters": fmt.Sprintf(`{"content": %s}`, content), } signature := getSign(params, config.sk+"&") reqParams := url.Values{} @@ -400,10 +401,10 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AISecurityConfig, body [ jsonData = []byte(denyMessage) } else if strings.Contains(strings.Join(hdsMap["content-type"], ";"), "event-stream") { randomID := generateRandomID() - jsonData = []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, respAdvice.Array()[0].Get("Answer").String(), randomID, model)) + jsonData = []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, denyMessage, randomID, model)) } else { randomID := generateRandomID() - jsonData = []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, model, respAdvice.Array()[0].Get("Answer").String())) + jsonData = []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, model, denyMessage)) } delete(hdsMap, "content-length") hdsMap[":status"] = []string{fmt.Sprint(config.denyCode)} @@ -432,10 +433,10 @@ func extractMessageFromStreamingBody(data []byte, jsonPath string) string { strChunks := []string{} for _, chunk := range chunks { // Example: "choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"logprobs":null,"finish_reason":null}] - jsonObj := gjson.GetBytes(chunk, jsonPath) - if jsonObj.Exists() { - strChunks = append(strChunks, jsonObj.String()) + jsonRaw := gjson.GetBytes(chunk, jsonPath).Raw + if len(jsonRaw) > 2 { + strChunks = append(strChunks, jsonRaw[1:len(jsonRaw)-1]) } } - return strings.Join(strChunks, "") + return fmt.Sprintf(`"%s"`, strings.Join(strChunks, "")) } From d96994767c92a1fe4e802359508ab4a20eddfc9e Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Mon, 21 Oct 2024 09:44:01 +0800 Subject: [PATCH 17/64] Change http_content to Rc in HttpWrapper (#1391) --- plugins/wasm-rust/Cargo.lock | 50 +++-- plugins/wasm-rust/Cargo.toml | 1 + .../extensions/ai-data-masking/Cargo.lock | 137 +++++++------ .../extensions/ai-data-masking/src/lib.rs | 4 +- .../wasm-rust/extensions/demo-wasm/Cargo.lock | 50 +++-- .../wasm-rust/extensions/demo-wasm/src/lib.rs | 109 +++++------ .../extensions/request-block/Cargo.lock | 93 ++++++--- .../extensions/request-block/src/lib.rs | 8 +- .../wasm-rust/extensions/say-hello/Cargo.lock | 81 ++++++-- plugins/wasm-rust/src/plugin_wrapper.rs | 184 ++++++++++-------- plugins/wasm-rust/src/rule_matcher.rs | 25 +-- 11 files changed, 442 insertions(+), 300 deletions(-) diff --git a/plugins/wasm-rust/Cargo.lock b/plugins/wasm-rust/Cargo.lock index 899d559b0d..9cf5140c77 100644 --- a/plugins/wasm-rust/Cargo.lock +++ b/plugins/wasm-rust/Cargo.lock @@ -32,6 +32,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "fnv" version = "1.0.7" @@ -63,6 +69,7 @@ dependencies = [ name = "higress-wasm-rust" version = "0.1.0" dependencies = [ + "downcast-rs", "http", "lazy_static", "multimap", @@ -97,9 +104,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "log" @@ -124,15 +131,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -140,17 +147,18 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#73833051f57d483570cf5aaa9d62bd7402fae63b" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" dependencies = [ + "downcast-rs", "hashbrown", "log", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -163,18 +171,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -183,9 +191,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -195,9 +203,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -206,15 +214,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] diff --git a/plugins/wasm-rust/Cargo.toml b/plugins/wasm-rust/Cargo.toml index a1e5472c63..e4fc274311 100644 --- a/plugins/wasm-rust/Cargo.toml +++ b/plugins/wasm-rust/Cargo.toml @@ -13,3 +13,4 @@ uuid = { version = "1.3.3", features = ["v4"] } multimap = "0" http = "1" lazy_static = "1" +downcast-rs="1" diff --git a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock b/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock index 853fde94fb..914d5b9689 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock +++ b/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock @@ -82,11 +82,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + [[package]] name = "cc" -version = "1.1.11" +version = "1.1.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fb8dd288a69fc53a1996d7ecfbf4a20d59065bff137ce7e56bbd620de191189" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" dependencies = [ "shlex", ] @@ -108,9 +114,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -162,18 +168,18 @@ dependencies = [ [[package]] name = "derive_builder" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0350b5cb0331628a5916d6c5c0b72e97393b8b6b03b47a9284f4e7f5a405ffd7" +checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d48cda787f839151732d396ac69e3473923d54312c070ee21e9effcaa8ca0b1d" +checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", @@ -183,9 +189,9 @@ dependencies = [ [[package]] name = "derive_builder_macro" -version = "0.20.0" +version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206868b8242f27cecce124c19fd88157fbd0dd334df2587f36417bafbc85097b" +checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", "syn", @@ -201,6 +207,12 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "fancy-regex" version = "0.13.0" @@ -278,6 +290,9 @@ dependencies = [ name = "higress-wasm-rust" version = "0.1.0" dependencies = [ + "downcast-rs", + "http", + "lazy_static", "multimap", "proxy-wasm", "serde", @@ -285,6 +300,17 @@ dependencies = [ "uuid", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -314,9 +340,9 @@ dependencies = [ [[package]] name = "jsonpath-rust" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d64f9886fc067a709ab27faf63b7d3f4d1ec570a700705408b0b0683e2f43897" +checksum = "514f8a353ad9e85443b30fefe169ce93794ec7c98054a4312ab08530f15b7bb3" dependencies = [ "pest", "pest_derive", @@ -333,9 +359,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "log" @@ -366,9 +392,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "onig" @@ -394,9 +420,9 @@ dependencies = [ [[package]] name = "pest" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd53dff83f26735fdc1ca837098ccf133605d794cdae66acfc2bfac3ec809d95" +checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" dependencies = [ "memchr", "thiserror", @@ -405,9 +431,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a548d2beca6773b1c244554d36fcf8548a8a58e74156968211567250e48e49a" +checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" dependencies = [ "pest", "pest_generator", @@ -415,9 +441,9 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c93a82e8d145725dcbaf44e5ea887c8a869efdcc28706df2d08c69e17077183" +checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" dependencies = [ "pest", "pest_meta", @@ -428,9 +454,9 @@ dependencies = [ [[package]] name = "pest_meta" -version = "2.7.11" +version = "2.7.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a941429fea7e08bedec25e4f6785b6ffaacc6b755da98df5ef3e7dcf4a124c4f" +checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" dependencies = [ "once_cell", "pest", @@ -477,15 +503,15 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -493,17 +519,18 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#73833051f57d483570cf5aaa9d62bd7402fae63b" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" dependencies = [ + "downcast-rs", "hashbrown", "log", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -525,9 +552,9 @@ checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -537,9 +564,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -548,9 +575,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rust-embed" @@ -603,18 +630,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -623,9 +650,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -670,9 +697,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.74" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -681,18 +708,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" dependencies = [ "proc-macro2", "quote", @@ -707,21 +734,21 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] diff --git a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs index a6114bdd8b..01573585ac 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs +++ b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs @@ -65,7 +65,7 @@ struct AiDataMaskingRoot { rule_matcher: SharedRuleMatcher, } struct AiDataMasking { - config: Option, + config: Option>, mask_map: HashMap>, is_openai: bool, stream: bool, @@ -585,7 +585,7 @@ impl HttpContext for AiDataMasking { } } impl HttpContextWrapper for AiDataMasking { - fn on_config(&mut self, config: &AiDataMaskingConfig) { + fn on_config(&mut self, config: Rc) { self.config = Some(config.clone()); } fn cache_request_body(&self) -> bool { diff --git a/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock b/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock index 85e2edaea3..c1e197caf6 100644 --- a/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock +++ b/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock @@ -43,6 +43,12 @@ dependencies = [ "serde", ] +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "fnv" version = "1.0.7" @@ -74,6 +80,7 @@ dependencies = [ name = "higress-wasm-rust" version = "0.1.0" dependencies = [ + "downcast-rs", "http", "lazy_static", "multimap", @@ -108,9 +115,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.157" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374af5f94e54fa97cf75e945cce8a6b201e88a1a07e688b47dfd2a59c66dbd86" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "log" @@ -135,15 +142,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -151,17 +158,18 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#73833051f57d483570cf5aaa9d62bd7402fae63b" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" dependencies = [ + "downcast-rs", "hashbrown", "log", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -174,18 +182,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cff085d2cb684faa248efb494c39b68e522822ac0de72ccf08109abde717cfb2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.208" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24008e81ff7613ed8e5ba0cfaf24e2c2f1e5b8a0495711e44fcd4882fca62bcf" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -194,9 +202,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -206,9 +214,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.75" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6af063034fc1935ede7be0122941bafa9bacb949334d090b77ca98b5817c7d9" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -217,15 +225,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] diff --git a/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs b/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs index 55647a83cc..62dfa055d0 100644 --- a/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs +++ b/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs @@ -1,8 +1,6 @@ use higress_wasm_rust::cluster_wrapper::DnsCluster; use higress_wasm_rust::log::Log; -use higress_wasm_rust::plugin_wrapper::{ - HttpCallArgStorage, HttpCallbackFn, HttpContextWrapper, RootContextWrapper, -}; +use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper}; use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher}; use http::Method; use multimap::MultiMap; @@ -12,7 +10,7 @@ use proxy_wasm::types::{Bytes, ContextType, DataAction, HeaderAction, LogLevel}; use serde::Deserialize; use std::cell::RefCell; use std::ops::DerefMut; -use std::rc::Rc; +use std::rc::{Rc, Weak}; use std::time::Duration; proxy_wasm::main! {{ @@ -37,40 +35,27 @@ fn format_body(body: Option>) -> String { format!("{:?}", body) } -fn test_callback( - this: &mut DemoWasm, - status_code: u16, - headers: &MultiMap, - body: Option>, -) { - this.log.info(&format!( - "test_callback status_code:{}, headers: {:?}, body: {}", - status_code, - headers, - format_body(body) - )); - this.reset_http_request(); -} struct DemoWasm { // 每个请求对应的插件实例 log: Log, - config: Option, - - arg_storage: HttpCallArgStorage>>, + config: Option>, + weak: Weak>>>, } impl Context for DemoWasm {} impl HttpContext for DemoWasm {} -impl HttpContextWrapper>> for DemoWasm { +impl HttpContextWrapper for DemoWasm { + fn init_self_weak( + &mut self, + self_weak: Weak>>>, + ) { + self.weak = self_weak; + self.log.info("init_self_rc"); + } fn log(&self) -> &Log { &self.log } - fn get_http_call_storage( - &mut self, - ) -> Option<&mut HttpCallArgStorage>>> { - Some(&mut self.arg_storage) - } - fn on_config(&mut self, config: &DemoWasmConfig) { + fn on_config(&mut self, config: Rc) { // 获取config self.log.info(&format!("on_config {}", config.test)); self.config = Some(config.clone()) @@ -101,16 +86,6 @@ impl HttpContextWrapper>> for DemoW // 是否缓存返回body true } - fn on_http_call_response_detail( - &mut self, - _token_id: u32, - arg: Box>, - status_code: u16, - headers: &MultiMap, - body: Option>, - ) { - arg(self, status_code, headers, body) - } fn on_http_request_complete_body(&mut self, req_body: &Bytes) -> DataAction { // 请求body获取完成回调 self.log.info(&format!( @@ -118,23 +93,41 @@ impl HttpContextWrapper>> for DemoW String::from_utf8(req_body.clone()).unwrap_or("".to_string()) )); let cluster = DnsCluster::new("httpbin", "httpbin.org", 80); - if self - .http_call( - &cluster, - &Method::POST, - "http://httpbin.org/post", - MultiMap::new(), - Some("test_body".as_bytes()), - // Box::new(move |this, _status_code, _headers, _body| this.resume_http_request()), - Box::new(test_callback), - Duration::from_secs(5), - ) - .is_ok() - { - DataAction::StopIterationAndBuffer - } else { - self.log.info("http_call fail"); - DataAction::Continue + + let self_rc = match self.weak.upgrade() { + Some(rc) => rc.clone(), + None => { + self.log.error("self_weak upgrade error"); + return DataAction::Continue; + } + }; + let http_call_res = self.http_call( + &cluster, + &Method::POST, + "http://httpbin.org/post", + MultiMap::new(), + Some("test_body".as_bytes()), + Box::new(move |status_code, headers, body| { + if let Some(this) = self_rc.borrow().downcast_ref::() { + this.log.info(&format!( + "test_callback status_code:{}, headers: {:?}, body: {}", + status_code, + headers, + format_body(body) + )); + this.resume_http_request(); + } else { + self_rc.borrow().resume_http_request(); + } + }), + Duration::from_secs(5), + ); + match http_call_res { + Ok(_) => DataAction::StopIterationAndBuffer, + Err(e) => { + self.log.info(&format!("http_call fail {:?}", e)); + DataAction::Continue + } } } fn on_http_response_complete_body(&mut self, res_body: &Bytes) -> DataAction { @@ -185,7 +178,7 @@ impl RootContext for DemoWasmRoot { } } -impl RootContextWrapper>> for DemoWasmRoot { +impl RootContextWrapper for DemoWasmRoot { fn rule_matcher(&self) -> &SharedRuleMatcher { &self.rule_matcher } @@ -193,11 +186,11 @@ impl RootContextWrapper>> for DemoW fn create_http_context_wrapper( &self, _context_id: u32, - ) -> Option>>>> { + ) -> Option>> { Some(Box::new(DemoWasm { config: None, log: Log::new(PLUGIN_NAME.to_string()), - arg_storage: HttpCallArgStorage::new(), + weak: Weak::default(), })) } } diff --git a/plugins/wasm-rust/extensions/request-block/Cargo.lock b/plugins/wasm-rust/extensions/request-block/Cargo.lock index acc9caeb26..243fe87a49 100644 --- a/plugins/wasm-rust/extensions/request-block/Cargo.lock +++ b/plugins/wasm-rust/extensions/request-block/Cargo.lock @@ -29,12 +29,30 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.2.15" @@ -60,6 +78,9 @@ dependencies = [ name = "higress-wasm-rust" version = "0.1.0" dependencies = [ + "downcast-rs", + "http", + "lazy_static", "multimap", "proxy-wasm", "serde", @@ -67,17 +88,34 @@ dependencies = [ "uuid", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "log" @@ -102,15 +140,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -118,26 +156,27 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#73833051f57d483570cf5aaa9d62bd7402fae63b" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" dependencies = [ + "downcast-rs", "hashbrown", "log", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] [[package]] name = "regex" -version = "1.10.6" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" dependencies = [ "aho-corasick", "memchr", @@ -147,9 +186,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" dependencies = [ "aho-corasick", "memchr", @@ -158,9 +197,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "request-block" @@ -182,18 +221,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -202,9 +241,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -214,9 +253,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -225,15 +264,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] diff --git a/plugins/wasm-rust/extensions/request-block/src/lib.rs b/plugins/wasm-rust/extensions/request-block/src/lib.rs index 38d50de187..d120acb902 100644 --- a/plugins/wasm-rust/extensions/request-block/src/lib.rs +++ b/plugins/wasm-rust/extensions/request-block/src/lib.rs @@ -41,7 +41,7 @@ struct RquestBlockRoot { struct RquestBlock { log: Log, - config: Option, + config: Option>, cache_request: bool, } @@ -141,9 +141,9 @@ impl RootContextWrapper for RquestBlockRoot { impl Context for RquestBlock {} impl HttpContext for RquestBlock {} impl HttpContextWrapper for RquestBlock { - fn on_config(&mut self, _config: &RquestBlockConfig) { - self.config = Some(_config.clone()); - self.cache_request = !_config.block_bodies.is_empty(); + fn on_config(&mut self, config: Rc) { + self.cache_request = !config.block_bodies.is_empty(); + self.config = Some(config.clone()); } fn cache_request_body(&self) -> bool { self.cache_request diff --git a/plugins/wasm-rust/extensions/say-hello/Cargo.lock b/plugins/wasm-rust/extensions/say-hello/Cargo.lock index 5a50473fcd..dc98fd709a 100644 --- a/plugins/wasm-rust/extensions/say-hello/Cargo.lock +++ b/plugins/wasm-rust/extensions/say-hello/Cargo.lock @@ -20,12 +20,30 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.2.15" @@ -51,6 +69,9 @@ dependencies = [ name = "higress-wasm-rust" version = "0.1.0" dependencies = [ + "downcast-rs", + "http", + "lazy_static", "multimap", "proxy-wasm", "serde", @@ -58,17 +79,34 @@ dependencies = [ "uuid", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" -version = "0.2.155" +version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" [[package]] name = "log" @@ -93,15 +131,15 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.88" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" dependencies = [ "unicode-ident", ] @@ -109,17 +147,18 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#73833051f57d483570cf5aaa9d62bd7402fae63b" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" dependencies = [ + "downcast-rs", "hashbrown", "log", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -142,18 +181,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", @@ -162,9 +201,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -174,9 +213,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -185,15 +224,15 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "uuid" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" dependencies = [ "getrandom", ] diff --git a/plugins/wasm-rust/src/plugin_wrapper.rs b/plugins/wasm-rust/src/plugin_wrapper.rs index 25d445f22f..5a00c0bda4 100644 --- a/plugins/wasm-rust/src/plugin_wrapper.rs +++ b/plugins/wasm-rust/src/plugin_wrapper.rs @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. +use std::cell::RefCell; use std::collections::HashMap; +use std::rc::{Rc, Weak}; use std::time::Duration; use crate::cluster_wrapper::Cluster; @@ -28,10 +30,13 @@ use serde::de::DeserializeOwned; lazy_static! { static ref LOG: Log = Log::new("plugin_wrapper".to_string()); } +thread_local! { + static HTTP_CALLBACK_DISPATCHER: HttpCallbackDispatcher = HttpCallbackDispatcher::new(); +} -pub trait RootContextWrapper: RootContext +pub trait RootContextWrapper: RootContext where - PluginConfig: Default + DeserializeOwned + 'static + Clone, + PluginConfig: Default + DeserializeOwned + Clone + 'static, { // fn create_http_context(&self, context_id: u32) -> Option> { fn create_http_context_use_wrapper(&self, context_id: u32) -> Option> { @@ -48,38 +53,48 @@ where fn create_http_context_wrapper( &self, _context_id: u32, - ) -> Option>> { + ) -> Option>> { None } } -pub type HttpCallbackFn = dyn FnOnce(&mut T, u16, &MultiMap, Option>); -pub struct HttpCallArgStorage { - args: HashMap, +pub type HttpCallbackFn = dyn FnOnce(u16, &MultiMap, Option>); + +pub struct HttpCallbackDispatcher { + call_fns: RefCell>>, } -impl Default for HttpCallArgStorage { +impl Default for HttpCallbackDispatcher { fn default() -> Self { Self::new() } } -impl HttpCallArgStorage { +impl HttpCallbackDispatcher { pub fn new() -> Self { - HttpCallArgStorage { - args: HashMap::new(), + HttpCallbackDispatcher { + call_fns: RefCell::new(HashMap::new()), } } - pub fn set(&mut self, token_id: u32, arg: HttpCallArg) { - self.args.insert(token_id, arg); + pub fn set(&self, token_id: u32, arg: Box) { + self.call_fns.borrow_mut().insert(token_id, arg); } - pub fn pop(&mut self, token_id: u32) -> Option { - self.args.remove(&token_id) + pub fn pop(&self, token_id: u32) -> Option> { + self.call_fns.borrow_mut().remove(&token_id) } } -pub trait HttpContextWrapper: HttpContext { + +pub trait HttpContextWrapper: HttpContext +where + PluginConfig: Default + DeserializeOwned + Clone + 'static, +{ + fn init_self_weak( + &mut self, + _self_weak: Weak>>>, + ) { + } fn log(&self) -> &Log { &LOG } - fn on_config(&mut self, _config: &PluginConfig) {} + fn on_config(&mut self, _config: Rc) {} fn on_http_request_complete_headers( &mut self, _headers: &MultiMap, @@ -105,16 +120,6 @@ pub trait HttpContextWrapper: HttpContext { DataAction::Continue } - #[allow(clippy::too_many_arguments)] - fn on_http_call_response_detail( - &mut self, - _token_id: u32, - _arg: HttpCallArg, - _status_code: u16, - _headers: &MultiMap, - _body: Option>, - ) { - } fn replace_http_request_body(&mut self, body: &[u8]) { self.set_http_request_body(0, i32::MAX as usize, body) } @@ -122,10 +127,6 @@ pub trait HttpContextWrapper: HttpContext { self.set_http_response_body(0, i32::MAX as usize, body) } - fn get_http_call_storage(&mut self) -> Option<&mut HttpCallArgStorage> { - None - } - #[allow(clippy::too_many_arguments)] fn http_call( &mut self, @@ -134,7 +135,7 @@ pub trait HttpContextWrapper: HttpContext { raw_url: &str, headers: MultiMap, body: Option<&[u8]>, - arg: HttpCallArg, + call_fn: Box, timeout: Duration, ) -> Result { if let Ok(uri) = raw_url.parse::() { @@ -162,17 +163,13 @@ pub trait HttpContextWrapper: HttpContext { ); if let Ok(token_id) = ret { - if let Some(storage) = self.get_http_call_storage() { - storage.set(token_id, arg); - self.log().debug( - &format!( - "http call start, id: {}, cluster: {}, method: {}, url: {}, body: {:?}, timeout: {:?}", - token_id, cluster.cluster_name(), method.as_str(), raw_url, body, timeout - ) - ); - } else { - return Err(Status::InternalFailure); - } + HTTP_CALLBACK_DISPATCHER.with(|dispatcher| dispatcher.set(token_id, call_fn)); + self.log().debug( + &format!( + "http call start, id: {}, cluster: {}, method: {}, url: {}, body: {:?}, timeout: {:?}", + token_id, cluster.cluster_name(), method.as_str(), raw_url, body, timeout + ) + ); } ret } else { @@ -181,20 +178,30 @@ pub trait HttpContextWrapper: HttpContext { } } } -pub struct PluginHttpWrapper { + +downcast_rs::impl_downcast!(HttpContextWrapper where PluginConfig: Default + DeserializeOwned + Clone); + +pub struct PluginHttpWrapper { req_headers: MultiMap, res_headers: MultiMap, req_body_len: usize, res_body_len: usize, - config: Option, + config: Option>, rule_matcher: SharedRuleMatcher, - http_content: Box>, + http_content: Rc>>>, } -impl PluginHttpWrapper { +impl PluginHttpWrapper +where + PluginConfig: Default + DeserializeOwned + Clone + 'static, +{ pub fn new( rule_matcher: &SharedRuleMatcher, - http_content: Box>, + http_content: Box>, ) -> Self { + let rc_content = Rc::new(RefCell::new(http_content)); + rc_content + .borrow_mut() + .init_self_weak(Rc::downgrade(&rc_content)); PluginHttpWrapper { req_headers: MultiMap::new(), res_headers: MultiMap::new(), @@ -202,18 +209,17 @@ impl PluginHttpWrapper { res_body_len: 0, config: None, rule_matcher: rule_matcher.clone(), - http_content, + http_content: rc_content, } } - fn get_http_call_arg(&mut self, token_id: u32) -> Option { - if let Some(storage) = self.http_content.get_http_call_storage() { - storage.pop(token_id) - } else { - None - } + fn get_http_call_fn(&mut self, token_id: u32) -> Option> { + HTTP_CALLBACK_DISPATCHER.with(|dispatcher| dispatcher.pop(token_id)) } } -impl Context for PluginHttpWrapper { +impl Context for PluginHttpWrapper +where + PluginConfig: Default + DeserializeOwned + Clone + 'static, +{ fn on_http_call_response( &mut self, token_id: u32, @@ -221,7 +227,7 @@ impl Context for PluginHttpWrapper Context for PluginHttpWrapper Context for PluginHttpWrapper { - self.http_content.log().warn(&format!( + self.http_content.borrow().log().warn(&format!( "http call response header contains non-ASCII characters header: {}", k )); } } } - self.http_content.log().warn(&format!( - "http call end, id: {}, code: {}, normal: {}, body: {:?}", + self.http_content.borrow().log().warn(&format!( + "http call end, id: {}, code: {}, normal: {}, body: {:?}", /* */ token_id, status_code, normal_response, body )); - self.http_content.on_http_call_response_detail( + call_fn(status_code, &headers, body) + } else { + self.http_content.borrow_mut().on_http_call_response( token_id, - arg, - status_code, - &headers, - body, + num_headers, + body_size, + num_trailers, ) - } else { - self.http_content - .on_http_call_response(token_id, num_headers, body_size, num_trailers) } } fn on_grpc_call_response(&mut self, token_id: u32, status_code: u32, response_size: usize) { self.http_content + .borrow_mut() .on_grpc_call_response(token_id, status_code, response_size) } fn on_grpc_stream_initial_metadata(&mut self, token_id: u32, num_elements: u32) { self.http_content + .borrow_mut() .on_grpc_stream_initial_metadata(token_id, num_elements) } fn on_grpc_stream_message(&mut self, token_id: u32, message_size: usize) { self.http_content + .borrow_mut() .on_grpc_stream_message(token_id, message_size) } fn on_grpc_stream_trailing_metadata(&mut self, token_id: u32, num_elements: u32) { self.http_content + .borrow_mut() .on_grpc_stream_trailing_metadata(token_id, num_elements) } fn on_grpc_stream_close(&mut self, token_id: u32, status_code: u32) { self.http_content + .borrow_mut() .on_grpc_stream_close(token_id, status_code) } fn on_done(&mut self) -> bool { - self.http_content.on_done() + self.http_content.borrow_mut().on_done() } } -impl HttpContext for PluginHttpWrapper +impl HttpContext for PluginHttpWrapper where - PluginConfig: Default + DeserializeOwned + Clone, + PluginConfig: Default + DeserializeOwned + Clone + 'static, { fn on_http_request_headers(&mut self, num_headers: usize, end_of_stream: bool) -> HeaderAction { let binding = self.rule_matcher.borrow(); @@ -306,7 +316,7 @@ where self.req_headers.insert(k, header_value); } Err(_) => { - self.http_content.log().warn(&format!( + self.http_content.borrow().log().warn(&format!( "request http header contains non-ASCII characters header: {}", k )); @@ -315,22 +325,25 @@ where } if let Some(config) = &self.config { - self.http_content.on_config(config); + self.http_content.borrow_mut().on_config(config.clone()); } let ret = self .http_content + .borrow_mut() .on_http_request_headers(num_headers, end_of_stream); if ret != HeaderAction::Continue { return ret; } self.http_content + .borrow_mut() .on_http_request_complete_headers(&self.req_headers) } fn on_http_request_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction { - if !self.http_content.cache_request_body() { + if !self.http_content.borrow().cache_request_body() { return self .http_content + .borrow_mut() .on_http_request_body(body_size, end_of_stream); } self.req_body_len += body_size; @@ -343,11 +356,15 @@ where req_body = body; } } - self.http_content.on_http_request_complete_body(&req_body) + self.http_content + .borrow_mut() + .on_http_request_complete_body(&req_body) } fn on_http_request_trailers(&mut self, num_trailers: usize) -> Action { - self.http_content.on_http_request_trailers(num_trailers) + self.http_content + .borrow_mut() + .on_http_request_trailers(num_trailers) } fn on_http_response_headers( @@ -361,7 +378,7 @@ where self.res_headers.insert(k, header_value); } Err(_) => { - self.http_content.log().warn(&format!( + self.http_content.borrow().log().warn(&format!( "response http header contains non-ASCII characters header: {}", k )); @@ -371,18 +388,21 @@ where let ret = self .http_content + .borrow_mut() .on_http_response_headers(num_headers, end_of_stream); if ret != HeaderAction::Continue { return ret; } self.http_content + .borrow_mut() .on_http_response_complete_headers(&self.res_headers) } fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction { - if !self.http_content.cache_response_body() { + if !self.http_content.borrow().cache_response_body() { return self .http_content + .borrow_mut() .on_http_response_body(body_size, end_of_stream); } self.res_body_len += body_size; @@ -397,14 +417,18 @@ where res_body = body; } } - self.http_content.on_http_response_complete_body(&res_body) + self.http_content + .borrow_mut() + .on_http_response_complete_body(&res_body) } fn on_http_response_trailers(&mut self, num_trailers: usize) -> Action { - self.http_content.on_http_response_trailers(num_trailers) + self.http_content + .borrow_mut() + .on_http_response_trailers(num_trailers) } fn on_log(&mut self) { - self.http_content.on_log() + self.http_content.borrow_mut().on_log() } } diff --git a/plugins/wasm-rust/src/rule_matcher.rs b/plugins/wasm-rust/src/rule_matcher.rs index 6478344334..dc42b433fc 100644 --- a/plugins/wasm-rust/src/rule_matcher.rs +++ b/plugins/wasm-rust/src/rule_matcher.rs @@ -20,6 +20,7 @@ use proxy_wasm::traits::RootContext; use proxy_wasm::types::LogLevel; use serde::de::DeserializeOwned; use serde_json::{from_slice, Map, Value}; +use std::borrow::Borrow; use std::cell::RefCell; use std::collections::HashSet; use std::rc::Rc; @@ -50,13 +51,13 @@ struct RuleConfig { category: Category, routes: HashSet, hosts: Vec, - config: PluginConfig, + config: Rc, } #[derive(Default)] pub struct RuleMatcher { rule_config: Vec>, - global_config: Option, + global_config: Option>, } impl RuleMatcher @@ -71,7 +72,7 @@ where let mut key_count = object.len(); if object.is_empty() { - self.global_config = Some(PluginConfig::default()); + self.global_config = Some(Rc::new(PluginConfig::default())); return Ok(()); } @@ -86,7 +87,7 @@ where if key_count > 0 { match serde_json::from_value::(config.clone()) { Ok(plugin_config) => { - self.global_config = Some(plugin_config); + self.global_config = Some(Rc::new(plugin_config)); } Err(err) => { log( @@ -134,14 +135,14 @@ where category, routes, hosts, - config, + config: Rc::new(config), }) } Ok(()) } - pub fn get_match_config(&self) -> Option<(i64, &PluginConfig)> { + pub fn get_match_config(&self) -> Option<(i64, Rc)> { let host = get_http_request_header(":authority").unwrap_or_default(); let route_name = get_property(vec!["route_name"]).unwrap_or_default(); @@ -149,7 +150,7 @@ where match rule.category { Category::Host => { if self.host_match(rule, host.as_str()) { - return Some((i as i64, &rule.config)); + return Some((i as i64, rule.config.clone())); } } Category::Route => { @@ -158,7 +159,7 @@ where .unwrap_or_else(|_| "".to_string()) .as_str(), ) { - return Some((i as i64, &rule.config)); + return Some((i as i64, rule.config.clone())); } } } @@ -166,14 +167,16 @@ where self.global_config .as_ref() - .map(|config| (usize::MAX as i64, config)) + .map(|config| (usize::MAX as i64, config.clone())) } pub fn rewrite_config(&mut self, rewrite: fn(config: &PluginConfig) -> PluginConfig) { - self.global_config = self.global_config.as_ref().map(rewrite); + if let Some(global_config) = &self.global_config { + self.global_config = Some(Rc::new(rewrite(global_config.borrow()))); + } for rule_config in &mut self.rule_config { - rule_config.config = rewrite(&rule_config.config); + rule_config.config = Rc::new(rewrite(rule_config.config.borrow())); } } From fc6902ded20a4bb0c3abf5e82ae271c59fd075a6 Mon Sep 17 00:00:00 2001 From: Ikko Eltociear Ashimine Date: Mon, 21 Oct 2024 16:20:45 +0900 Subject: [PATCH 18/64] docs: add Japanese README and CONTRIBUTING files (#1407) --- CONTRIBUTING_JP.md | 195 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 2 +- README_EN.md | 2 +- README_JP.md | 189 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 386 insertions(+), 2 deletions(-) create mode 100644 CONTRIBUTING_JP.md create mode 100644 README_JP.md diff --git a/CONTRIBUTING_JP.md b/CONTRIBUTING_JP.md new file mode 100644 index 0000000000..7916c56dd4 --- /dev/null +++ b/CONTRIBUTING_JP.md @@ -0,0 +1,195 @@ +# Higress への貢献 + +Higress のハッキングに興味がある場合は、温かく歓迎します。まず、このような意欲を非常に奨励します。そして、以下は貢献ガイドのリストです。 + +[[中文](./CONTRIBUTING.md)] | [[English Contributing Document](./CONTRIBUTING_EN.md)] + +## トピック + +- [Higress への貢献](#higress-への貢献) + - [トピック](#トピック) + - [セキュリティ問題の報告](#セキュリティ問題の報告) + - [一般的な問題の報告](#一般的な問題の報告) + - [コードとドキュメントの貢献](#コードとドキュメントの貢献) + - [ワークスペースの準備](#ワークスペースの準備) + - [ブランチの定義](#ブランチの定義) + - [コミットルール](#コミットルール) + - [コミットメッセージ](#コミットメッセージ) + - [コミット内容](#コミット内容) + - [PR 説明](#pr-説明) + - [テストケースの貢献](#テストケースの貢献) + - [何かを手伝うための参加](#何かを手伝うための参加) + - [コードスタイル](#コードスタイル) + +## セキュリティ問題の報告 + +セキュリティ問題は常に真剣に扱われます。通常の原則として、セキュリティ問題を広めることは推奨しません。Higress のセキュリティ問題を発見した場合は、公開で議論せず、公開の問題を開かないでください。代わりに、[higress@googlegroups.com](mailto:higress@googlegroups.com) にプライベートなメールを送信して報告することをお勧めします。 + +## 一般的な問題の報告 + +正直なところ、Higress のすべてのユーザーを非常に親切な貢献者と見なしています。Higress を体験した後、プロジェクトに対するフィードバックがあるかもしれません。その場合は、[NEW ISSUE](https://github.com/alibaba/higress/issues/new/choose) を通じて問題を開くことを自由に行ってください。 + +Higress プロジェクトを分散型で協力しているため、**よく書かれた**、**詳細な**、**明確な**問題報告を高く評価します。コミュニケーションをより効率的にするために、問題が検索リストに存在するかどうかを検索することを希望します。存在する場合は、新しい問題を開くのではなく、既存の問題のコメントに詳細を追加してください。 + +問題の詳細をできるだけ標準化するために、問題報告者のために [ISSUE TEMPLATE](./.github/ISSUE_TEMPLATE) を設定しました。テンプレートのフィールドに従って指示に従って記入してください。 + +問題を開く場合は多くのケースがあります: + +* バグ報告 +* 機能要求 +* パフォーマンス問題 +* 機能提案 +* 機能設計 +* 助けが必要 +* ドキュメントが不完全 +* テストの改善 +* プロジェクトに関する質問 +* その他 + +また、新しい問題を記入する際には、投稿から機密データを削除することを忘れないでください。機密データには、パスワード、秘密鍵、ネットワークの場所、プライベートなビジネスデータなどが含まれる可能性があります。 + +## コードとドキュメントの貢献 + +Higress プロジェクトをより良くするためのすべての行動が奨励されます。GitHub では、Higress のすべての改善は PR(プルリクエストの略)を通じて行うことができます。 + +* タイプミスを見つけた場合は、修正してみてください! +* バグを見つけた場合は、修正してみてください! +* 冗長なコードを見つけた場合は、削除してみてください! +* 欠落しているテストケースを見つけた場合は、追加してみてください! +* 機能を強化できる場合は、**ためらわないでください**! +* コードが不明瞭な場合は、コメントを追加して明確にしてください! +* コードが醜い場合は、リファクタリングしてみてください! +* ドキュメントの改善に役立つ場合は、さらに良いです! +* ドキュメントが不正確な場合は、修正してください! +* ... + +実際には、それらを完全にリストすることは不可能です。1つの原則を覚えておいてください: + +> あなたからの PR を楽しみにしています。 + +Higress を PR で改善する準備ができたら、ここで PR ルールを確認することをお勧めします。 + +* [ワークスペースの準備](#ワークスペースの準備) +* [ブランチの定義](#ブランチの定義) +* [コミットルール](#コミットルール) +* [PR 説明](#pr-説明) + +### ワークスペースの準備 + +PR を提出するために、GitHub ID に登録していることを前提とします。その後、以下の手順で準備を完了できます: + +1. Higress を自分のリポジトリに **FORK** します。この作業を行うには、[alibaba/higress](https://github.com/alibaba/higress) のメインページの右上にある Fork ボタンをクリックするだけです。その後、`https://github.com//higress` に自分のリポジトリが作成されます。ここで、`your-username` はあなたの GitHub ユーザー名です。 + +2. 自分のリポジトリをローカルに **CLONE** します。`git clone git@github.com:/higress.git` を使用してリポジトリをローカルマシンにクローンします。その後、新しいブランチを作成して、行いたい変更を完了できます。 + +3. リモートを `git@github.com:alibaba/higress.git` に設定します。以下の2つのコマンドを使用します: + +```bash +git remote add upstream git@github.com:alibaba/higress.git +git remote set-url --push upstream no-pushing +``` + +このリモート設定を使用すると、git リモート設定を次のように確認できます: + +```shell +$ git remote -v +origin git@github.com:/higress.git (fetch) +origin git@github.com:/higress.git (push) +upstream git@github.com:alibaba/higress.git (fetch) +upstream no-pushing (push) +``` + +これを追加すると、ローカルブランチを上流ブランチと簡単に同期できます。 + +### ブランチの定義 + +現在、プルリクエストを通じたすべての貢献は Higress の [main ブランチ](https://github.com/alibaba/higress/tree/main) に対するものであると仮定します。貢献する前に、ブランチの定義を理解することは非常に役立ちます。 + +貢献者として、プルリクエストを通じたすべての貢献は main ブランチに対するものであることを再度覚えておいてください。Higress プロジェクトには、リリースブランチ(例:0.6.0、0.6.1)、機能ブランチ、ホットフィックスブランチなど、いくつかの他のブランチがあります。 + +正式にバージョンをリリースする際には、リリースブランチが作成され、バージョン番号で命名されます。 + +リリース後、リリースブランチのコミットを main ブランチにマージします。 + +特定のバージョンにバグがある場合、後のバージョンで修正するか、特定のホットフィックスバージョンで修正するかを決定します。ホットフィックスバージョンで修正することを決定した場合、対応するリリースブランチに基づいてホットフィックスブランチをチェックアウトし、コード修正と検証を行い、main ブランチにマージします。 + +大きな機能については、開発と検証のために機能ブランチを引き出します。 + +### コミットルール + +実際には、Higress ではコミット時に2つのルールを真剣に考えています: + +* [コミットメッセージ](#コミットメッセージ) +* [コミット内容](#コミット内容) + +#### コミットメッセージ + +コミットメッセージは、提出された PR の目的をレビュアーがよりよく理解するのに役立ちます。また、コードレビューの手続きを加速するのにも役立ちます。貢献者には、曖昧なメッセージではなく、**明確な**コミットメッセージを使用することを奨励します。一般的に、以下のコミットメッセージタイプを推奨します: + +* docs: xxxx. 例:"docs: add docs about Higress cluster installation". +* feature: xxxx. 例:"feature: use higress config instead of istio config". +* bugfix: xxxx. 例:"bugfix: fix panic when input nil parameter". +* refactor: xxxx. 例:"refactor: simplify to make codes more readable". +* test: xxx. 例:"test: add unit test case for func InsertIntoArray". +* その他の読みやすく明確な表現方法。 + +一方で、以下のような方法でのコミットメッセージは推奨しません: + +* ~~バグ修正~~ +* ~~更新~~ +* ~~ドキュメント追加~~ + +迷った場合は、[Git コミットメッセージの書き方](http://chris.beams.io/posts/git-commit/) を参照してください。 + +#### コミット内容 + +コミット内容は、1つのコミットに含まれるすべての内容の変更を表します。1つのコミットに、他のコミットの助けを借りずにレビュアーが完全にレビューできる内容を含めるのが最善です。言い換えれば、1つのコミットの内容は CI を通過でき、コードの混乱を避けることができます。簡単に言えば、次の3つの小さなルールを覚えておく必要があります: + +* コミットで非常に大きな変更を避ける; +* 各コミットが完全でレビュー可能であること。 +* コミット時に git config(`user.name`、`user.email`)を確認して、それが GitHub ID に関連付けられていることを確認します。 + +```bash +git config --get user.name +git config --get user.email +``` + +* pr を提出する際には、'changes/' フォルダーの下の XXX.md ファイルに現在の変更の簡単な説明を追加してください。 + +さらに、コード変更部分では、すべての貢献者が Higress の [コードスタイル](#コードスタイル) を読むことをお勧めします。 + +コミットメッセージやコミット内容に関係なく、コードレビューに重点を置いています。 + +### PR 説明 + +PR は Higress プロジェクトファイルを変更する唯一の方法です。レビュアーが目的をよりよく理解できるようにするために、PR 説明は詳細すぎることはありません。貢献者には、[PR テンプレート](./.github/PULL_REQUEST_TEMPLATE.md) に従ってプルリクエストを完了することを奨励します。 + +### 開発前の準備 + +```shell +make prebuild && go mod tidy +``` + +## テストケースの貢献 + +テストケースは歓迎されます。現在、Higress の機能テストケースが高優先度です。 + +* 単体テストの場合、同じモジュールの test ディレクトリに xxxTest.go という名前のテストファイルを作成する必要があります。 +* 統合テストの場合、統合テストを test ディレクトリに配置できます。 +//TBD + +## 何かを手伝うための参加 + +GitHub を Higress の協力の主要な場所として選択しました。したがって、Higress の最新の更新は常にここにあります。PR を通じた貢献は明確な助けの方法ですが、他の方法も呼びかけています。 + +* 可能であれば、他の人の質問に返信する; +* 他のユーザーの問題を解決するのを手伝う; +* 他の人の PR 設計をレビューするのを手伝う; +* 他の人の PR のコードをレビューするのを手伝う; +* Higress について議論して、物事を明確にする; +* GitHub 以外で Higress 技術を宣伝する; +* Higress に関するブログを書くなど。 + +## コードスタイル +//TBD +要するに、**どんな助けも貢献です。** diff --git a/README.md b/README.md index 87468ed9ad..761f8591a8 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@

- English | 中文 + English | 中文 | 日本語

diff --git a/README_EN.md b/README_EN.md index 8ce33aa069..b099fa3e5b 100644 --- a/README_EN.md +++ b/README_EN.md @@ -15,7 +15,7 @@

- English | 中文 + English | 中文 | 日本語

Higress is a cloud-native api gateway based on Alibaba's internal gateway practices. diff --git a/README_JP.md b/README_JP.md new file mode 100644 index 0000000000..8573576ee0 --- /dev/null +++ b/README_JP.md @@ -0,0 +1,189 @@ +

+ Higress +
+ AIゲートウェイ +

+

AIネイティブAPIゲートウェイ

+ +[![Build Status](https://github.com/alibaba/higress/actions/workflows/build-and-test.yaml/badge.svg?branch=main)](https://github.com/alibaba/higress/actions) +[![license](https://img.shields.io/github/license/alibaba/higress.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) + +[**公式サイト**](https://higress.cn/)   | +  [**ドキュメント**](https://higress.cn/docs/latest/overview/what-is-higress/)   | +  [**ブログ**](https://higress.cn/blog/)   | +  [**電子書籍**](https://higress.cn/docs/ebook/wasm14/)   | +  [**開発ガイド**](https://higress.cn/docs/latest/dev/architecture/)   | +  [**AIプラグイン**](https://higress.cn/plugin/)   + + +

+ English | 中文 | 日本語 +

+ + +Higressは、IstioとEnvoyをベースにしたクラウドネイティブAPIゲートウェイで、Go/Rust/JSなどを使用してWasmプラグインを作成できます。数十の既製の汎用プラグインと、すぐに使用できるコンソールを提供しています(デモは[こちら](http://demo.higress.io/))。 + +Higressは、Tengineのリロードが長時間接続のビジネスに影響を与える問題や、gRPC/Dubboの負荷分散能力の不足を解決するために、Alibaba内部で誕生しました。 + +Alibaba Cloudは、Higressを基盤にクラウドネイティブAPIゲートウェイ製品を構築し、多くの企業顧客に99.99%のゲートウェイ高可用性保証サービスを提供しています。 + +Higressは、AIゲートウェイ機能を基盤に、Tongyi Qianwen APP、Bailian大規模モデルAPI、機械学習PAIプラットフォームなどのAIビジネスをサポートしています。また、国内の主要なAIGC企業(例:ZeroOne)やAI製品(例:FastGPT)にもサービスを提供しています。 + +![](https://img.alicdn.com/imgextra/i2/O1CN011AbR8023V8R5N0HcA_!!6000000007260-2-tps-1080-606.png) + + +## 目次 + +- [**クイックスタート**](#クイックスタート) +- [**機能紹介**](#機能紹介) +- [**使用シナリオ**](#使用シナリオ) +- [**主な利点**](#主な利点) +- [**コミュニティ**](#コミュニティ) + +## クイックスタート + +HigressはDockerだけで起動でき、個人開発者がローカルで学習用にセットアップしたり、簡易サイトを構築するのに便利です。 + +```bash +# 作業ディレクトリを作成 +mkdir higress; cd higress +# Higressを起動し、設定ファイルを作業ディレクトリに書き込みます +docker run -d --rm --name higress-ai -v ${PWD}:/data \ + -p 8001:8001 -p 8080:8080 -p 8443:8443 \ + higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/all-in-one:latest +``` + +リスンポートの説明は以下の通りです: + +- 8001ポート:Higress UIコンソールのエントリーポイント +- 8080ポート:ゲートウェイのHTTPプロトコルエントリーポイント +- 8443ポート:ゲートウェイのHTTPSプロトコルエントリーポイント + +**HigressのすべてのDockerイメージは専用のリポジトリを使用しており、Docker Hubの国内アクセス不可の影響を受けません** + +K8sでのHelmデプロイなどの他のインストール方法については、公式サイトの[クイックスタートドキュメント](https://higress.cn/docs/latest/user/quickstart/)を参照してください。 + + +## 使用シナリオ + +- **AIゲートウェイ**: + + Higressは、国内外のすべてのLLMモデルプロバイダーと統一されたプロトコルで接続でき、豊富なAI可観測性、多モデル負荷分散/フォールバック、AIトークンフロー制御、AIキャッシュなどの機能を備えています。 + + ![](https://img.alicdn.com/imgextra/i1/O1CN01fNnhCp1cV8mYPRFeS_!!6000000003605-0-tps-1080-608.jpg) + +- **Kubernetes Ingressゲートウェイ**: + + HigressはK8sクラスターのIngressエントリーポイントゲートウェイとして機能し、多くのK8s Nginx Ingressの注釈に対応しています。K8s Nginx IngressからHigressへのスムーズな移行が可能です。 + + [Gateway API](https://gateway-api.sigs.k8s.io/)標準をサポートし、ユーザーがIngress APIからGateway APIにスムーズに移行できるようにします。 + + ingress-nginxと比較して、リソースの消費が大幅に減少し、ルーティングの変更が10倍速く反映されます。 + + ![](https://img.alicdn.com/imgextra/i1/O1CN01bhEtb229eeMNBWmdP_!!6000000008093-2-tps-750-547.png) + ![](https://img.alicdn.com/imgextra/i1/O1CN01bqRets1LsBGyitj4S_!!6000000001354-2-tps-887-489.png) + +- **マイクロサービスゲートウェイ**: + + Higressはマイクロサービスゲートウェイとして機能し、Nacos、ZooKeeper、Consul、Eurekaなどのさまざまなサービスレジストリからサービスを発見し、ルーティングを構成できます。 + + また、[Dubbo](https://github.com/apache/dubbo)、[Nacos](https://github.com/alibaba/nacos)、[Sentinel](https://github.com/alibaba/Sentinel)などのマイクロサービス技術スタックと深く統合されています。Envoy C++ゲートウェイコアの優れたパフォーマンスに基づいて、従来のJavaベースのマイクロサービスゲートウェイと比較して、リソース使用率を大幅に削減し、コストを削減できます。 + + ![](https://img.alicdn.com/imgextra/i4/O1CN01v4ZbCj1dBjePSMZ17_!!6000000003698-0-tps-1613-926.jpg) + +- **セキュリティゲートウェイ**: + + Higressはセキュリティゲートウェイとして機能し、WAF機能を提供し、key-auth、hmac-auth、jwt-auth、basic-auth、oidcなどのさまざまな認証戦略をサポートします。 + +## 主な利点 + +- **プロダクションレベル** + + Alibabaで2年以上のプロダクション検証を経た内部製品から派生し、毎秒数十万のリクエストを処理する大規模なシナリオをサポートします。 + + Nginxのリロードによるトラフィックの揺れを完全に排除し、構成変更がミリ秒単位で反映され、ビジネスに影響を与えません。AIビジネスなどの長時間接続シナリオに特に適しています。 + +- **ストリーム処理** + + リクエスト/レスポンスボディの完全なストリーム処理をサポートし、Wasmプラグインを使用してSSE(Server-Sent Events)などのストリームプロトコルのメッセージをカスタマイズして処理できます。 + + AIビジネスなどの大帯域幅シナリオで、メモリ使用量を大幅に削減できます。 + +- **拡張性** + + AI、トラフィック管理、セキュリティ保護などの一般的な機能をカバーする豊富な公式プラグインライブラリを提供し、90%以上のビジネスシナリオのニーズを満たします。 + + Wasmプラグイン拡張を主力とし、サンドボックス隔離を通じてメモリの安全性を確保し、複数のプログラミング言語をサポートし、プラグインバージョンの独立したアップグレードを許可し、トラフィックに影響を与えずにゲートウェイロジックをホットアップデートできます。 + +- **安全で使いやすい** + + Ingress APIおよびGateway API標準に基づき、すぐに使用できるUIコンソールを提供し、WAF保護プラグイン、IP/Cookie CC保護プラグインをすぐに使用できます。 + + Let's Encryptの自動証明書発行および更新をサポートし、K8sを使用せずにデプロイでき、1行のDockerコマンドで起動でき、個人開発者にとって便利です。 + + +## 機能紹介 + +### AIゲートウェイデモ展示 + +[OpenAIから他の大規模モデルへの移行を30秒で完了 +](https://www.bilibili.com/video/BV1dT421a7w7/?spm_id_from=333.788.recommend_more_video.14) + + +### Higress UIコンソール + +- **豊富な可観測性** + + すぐに使用できる可観測性を提供し、Grafana&Prometheusは組み込みのものを使用することも、自分で構築したものを接続することもできます。 + + ![](./docs/images/monitor.gif) + + +- **プラグイン拡張メカニズム** + + 公式にはさまざまなプラグインが提供されており、ユーザーは[独自のプラグインを開発](./plugins/wasm-go)し、Docker/OCIイメージとして構築し、コンソールで構成して、プラグインロジックをリアルタイムで変更できます。トラフィックに影響を与えずにプラグインロジックをホットアップデートできます。 + + ![](./docs/images/plugin.gif) + + +- **さまざまなサービス発見** + + デフォルトでK8s Serviceサービス発見を提供し、構成を通じてNacos/ZooKeeperなどのレジストリに接続してサービスを発見することも、静的IPまたはDNSに基づいて発見することもできます。 + + ![](./docs/images/service-source.gif) + + +- **ドメインと証明書** + + TLS証明書を作成および管理し、ドメインのHTTP/HTTPS動作を構成できます。ドメインポリシーでは、特定のドメインに対してプラグインを適用することができます。 + + ![](./docs/images/domain.gif) + + +- **豊富なルーティング機能** + + 上記で定義されたサービス発見メカニズムを通じて、発見されたサービスはサービスリストに表示されます。ルーティングを作成する際に、ドメインを選択し、ルーティングマッチングメカニズムを定義し、ターゲットサービスを選択してルーティングを行います。ルーティングポリシーでは、特定のルーティングに対してプラグインを適用することができます。 + + ![](./docs/images/route-service.gif) + + +## コミュニティ + +### 感謝 + +EnvoyとIstioのオープンソースの取り組みがなければ、Higressは実現できませんでした。これらのプロジェクトに最も誠実な敬意を表します。 + +### 交流グループ + +![image](https://img.alicdn.com/imgextra/i2/O1CN01BkopaB22ZsvamFftE_!!6000000007135-0-tps-720-405.jpg) + +### 技術共有 + +WeChat公式アカウント: + +![](https://img.alicdn.com/imgextra/i1/O1CN01WnQt0q1tcmqVDU73u_!!6000000005923-0-tps-258-258.jpg) + +### 関連リポジトリ + +- Higressコンソール:https://github.com/higress-group/higress-console +- Higress(スタンドアロン版):https://github.com/higress-group/higress-standalone From badf4b7101526f2a8cb9dd7cc76d6cc06cb55e24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Mon, 21 Oct 2024 15:43:01 +0800 Subject: [PATCH 19/64] ai cache plugin support set skip ai cache header (#1380) --- plugins/wasm-go/extensions/ai-cache/README.md | 5 +++++ plugins/wasm-go/extensions/ai-cache/README_EN.md | 4 ++++ plugins/wasm-go/extensions/ai-cache/main.go | 12 ++++++++++++ 3 files changed, 21 insertions(+) diff --git a/plugins/wasm-go/extensions/ai-cache/README.md b/plugins/wasm-go/extensions/ai-cache/README.md index 97728f5177..1de252f12c 100644 --- a/plugins/wasm-go/extensions/ai-cache/README.md +++ b/plugins/wasm-go/extensions/ai-cache/README.md @@ -9,6 +9,11 @@ description: AI 缓存插件配置参考 LLM 结果缓存插件,默认配置方式可以直接用于 openai 协议的结果缓存,同时支持流式和非流式响应的缓存。 +**提示** + +携带请求头`x-higress-skip-ai-cache: on`时,当前请求将不会使用缓存中的内容,而是直接转发给后端服务,同时也不会缓存该请求返回响应的内容 + + ## 运行属性 插件执行阶段:`认证阶段` diff --git a/plugins/wasm-go/extensions/ai-cache/README_EN.md b/plugins/wasm-go/extensions/ai-cache/README_EN.md index 81099e509c..7544995999 100644 --- a/plugins/wasm-go/extensions/ai-cache/README_EN.md +++ b/plugins/wasm-go/extensions/ai-cache/README_EN.md @@ -6,6 +6,10 @@ description: AI Cache Plugin Configuration Reference ## Function Description LLM result caching plugin, the default configuration can be directly used for result caching under the OpenAI protocol, and it supports caching of both streaming and non-streaming responses. +**Tips** + +When carrying the request header `x-higress-skip-ai-cache: on`, the current request will not use content from the cache but will be directly forwarded to the backend service. Additionally, the response content from this request will not be cached. + ## Runtime Properties Plugin Execution Phase: `Authentication Phase` Plugin Execution Priority: `10` diff --git a/plugins/wasm-go/extensions/ai-cache/main.go b/plugins/wasm-go/extensions/ai-cache/main.go index dc5df1a6a8..7886d5698f 100644 --- a/plugins/wasm-go/extensions/ai-cache/main.go +++ b/plugins/wasm-go/extensions/ai-cache/main.go @@ -22,6 +22,7 @@ const ( ToolCallsContextKey = "toolCalls" StreamContextKey = "stream" DefaultCacheKeyPrefix = "higress-ai-cache:" + SkipCacheHeader = "x-higress-skip-ai-cache" ) func main() { @@ -172,6 +173,12 @@ func parseConfig(json gjson.Result, c *PluginConfig, log wrapper.Log) error { } func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action { + skipCache, _ := proxywasm.GetHttpRequestHeader(SkipCacheHeader) + if skipCache == "on" { + ctx.SetContext(SkipCacheHeader, struct{}{}) + ctx.DontReadRequestBody() + return types.ActionContinue + } contentType, _ := proxywasm.GetHttpRequestHeader("content-type") // The request does not have a body. if contentType == "" { @@ -270,6 +277,11 @@ func processSSEMessage(ctx wrapper.HttpContext, config PluginConfig, sseMessage } func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action { + skipCache := ctx.GetContext(SkipCacheHeader) + if skipCache != nil { + ctx.DontReadResponseBody() + return types.ActionContinue + } contentType, _ := proxywasm.GetHttpResponseHeader("content-type") if strings.Contains(contentType, "text/event-stream") { ctx.SetContext(StreamContextKey, struct{}{}) From f8d62a8ac3d8ff56b9784f4e41a120ff514ca979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Mon, 21 Oct 2024 16:46:18 +0800 Subject: [PATCH 20/64] add model router plugin (#1414) --- plugins/wasm-cpp/WORKSPACE | 6 +- plugins/wasm-cpp/bazel/wasm.bzl | 6 +- .../wasm-cpp/extensions/model_router/BUILD | 70 +++++++ .../extensions/model_router/README.md | 64 ++++++ .../extensions/model_router/README_EN.md | 63 ++++++ .../extensions/model_router/plugin.cc | 189 ++++++++++++++++++ .../wasm-cpp/extensions/model_router/plugin.h | 85 ++++++++ .../extensions/model_router/plugin_test.cc | 144 +++++++++++++ 8 files changed, 621 insertions(+), 6 deletions(-) create mode 100644 plugins/wasm-cpp/extensions/model_router/BUILD create mode 100644 plugins/wasm-cpp/extensions/model_router/README.md create mode 100644 plugins/wasm-cpp/extensions/model_router/README_EN.md create mode 100644 plugins/wasm-cpp/extensions/model_router/plugin.cc create mode 100644 plugins/wasm-cpp/extensions/model_router/plugin.h create mode 100644 plugins/wasm-cpp/extensions/model_router/plugin_test.cc diff --git a/plugins/wasm-cpp/WORKSPACE b/plugins/wasm-cpp/WORKSPACE index dc55483d9c..ed78d0df06 100644 --- a/plugins/wasm-cpp/WORKSPACE +++ b/plugins/wasm-cpp/WORKSPACE @@ -16,15 +16,15 @@ load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps") container_deps() -PROXY_WASM_CPP_SDK_SHA = "fd0be8405db25de0264bdb78fae3a82668c03782" +PROXY_WASM_CPP_SDK_SHA = "eaec483b5b3c7bcb89fd208b5a1fa5d79d626f61" -PROXY_WASM_CPP_SDK_SHA256 = "c57de2425b5c61d7f630c5061e319b4557ae1f1c7526e5a51c33dc1299471b08" +PROXY_WASM_CPP_SDK_SHA256 = "1140bc8114d75db56a6ca6b18423d4df50d988d40b4cec929a1eb246cf5a4a3d" http_archive( name = "proxy_wasm_cpp_sdk", sha256 = PROXY_WASM_CPP_SDK_SHA256, strip_prefix = "proxy-wasm-cpp-sdk-" + PROXY_WASM_CPP_SDK_SHA, - url = "https://github.com/proxy-wasm/proxy-wasm-cpp-sdk/archive/" + PROXY_WASM_CPP_SDK_SHA + ".tar.gz", + url = "https://github.com/higress-group/proxy-wasm-cpp-sdk/archive/" + PROXY_WASM_CPP_SDK_SHA + ".tar.gz", ) load("@proxy_wasm_cpp_sdk//bazel/dep:deps.bzl", "wasm_dependencies") diff --git a/plugins/wasm-cpp/bazel/wasm.bzl b/plugins/wasm-cpp/bazel/wasm.bzl index a100ccc0b6..1f061fbc69 100644 --- a/plugins/wasm-cpp/bazel/wasm.bzl +++ b/plugins/wasm-cpp/bazel/wasm.bzl @@ -33,14 +33,14 @@ def wasm_libraries(): urls = ["https://github.com/google/googletest/archive/release-1.10.0.tar.gz"], ) - PROXY_WASM_CPP_HOST_SHA = "f38347360feaaf5b2a733f219c4d8c9660d626f0" - PROXY_WASM_CPP_HOST_SHA256 = "bf10de946eb5785813895c2bf16504afc0cd590b9655d9ee52fb1074d0825ea3" + PROXY_WASM_CPP_HOST_SHA = "7850d1721fe3dd2ccfb86a06116f76c23b1f1bf8" + PROXY_WASM_CPP_HOST_SHA256 = "740690fc1d749849f6e24b5bc48a07dabc0565a7d03b6cd13425dba693956c57" http_archive( name = "proxy_wasm_cpp_host", sha256 = PROXY_WASM_CPP_HOST_SHA256, strip_prefix = "proxy-wasm-cpp-host-" + PROXY_WASM_CPP_HOST_SHA, - url = "https://github.com/proxy-wasm/proxy-wasm-cpp-host/archive/" + PROXY_WASM_CPP_HOST_SHA +".tar.gz", + url = "https://github.com/higress-group/proxy-wasm-cpp-host/archive/" + PROXY_WASM_CPP_HOST_SHA +".tar.gz", ) http_archive( diff --git a/plugins/wasm-cpp/extensions/model_router/BUILD b/plugins/wasm-cpp/extensions/model_router/BUILD new file mode 100644 index 0000000000..67cfa547db --- /dev/null +++ b/plugins/wasm-cpp/extensions/model_router/BUILD @@ -0,0 +1,70 @@ +# 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. + +load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("//bazel:wasm.bzl", "declare_wasm_image_targets") + +wasm_cc_binary( + name = "model_router.wasm", + srcs = [ + "plugin.cc", + "plugin.h", + ], + deps = [ + "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics_higress", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "//common:json_util", + "//common:http_util", + "//common:rule_util", + ], +) + +cc_library( + name = "model_router_lib", + srcs = [ + "plugin.cc", + ], + hdrs = [ + "plugin.h", + ], + copts = ["-DNULL_PLUGIN"], + deps = [ + "@com_google_absl//absl/strings", + "@com_google_absl//absl/time", + "//common:json_util", + "@proxy_wasm_cpp_host//:lib", + "//common:http_util_nullvm", + "//common:rule_util_nullvm", + ], +) + +cc_test( + name = "model_router_test", + srcs = [ + "plugin_test.cc", + ], + copts = ["-DNULL_PLUGIN"], + deps = [ + ":model_router_lib", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + "@proxy_wasm_cpp_host//:lib", + ], +) + +declare_wasm_image_targets( + name = "model_router", + wasm_file = ":model_router.wasm", +) diff --git a/plugins/wasm-cpp/extensions/model_router/README.md b/plugins/wasm-cpp/extensions/model_router/README.md new file mode 100644 index 0000000000..b63be35d8f --- /dev/null +++ b/plugins/wasm-cpp/extensions/model_router/README.md @@ -0,0 +1,64 @@ +## 功能说明 +`model-router`插件实现了基于LLM协议中的model参数路由的功能 + +## 运行属性 + +插件执行阶段:`默认阶段` +插件执行优先级:`260` + +## 配置字段 + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +| ----------- | --------------- | ----------------------- | ------ | ------------------------------------------- | +| `enable` | bool | 选填 | false | 是否开启基于model参数路由 | +| `model_key` | string | 选填 | model | 请求body中model参数的位置 | +| `add_header_key` | string | 选填 | x-higress-llm-provider | 从model参数中解析出的provider名字放到哪个请求header中 | + + +## 效果说明 + +如下开启基于model参数路由的功能: + +```yaml +enable: true +``` + +开启后,插件将请求中 model 参数的 provider 部分(如果有)提取出来,设置到 x-higress-llm-provider 这个请求 header 中,用于后续路由,并将 model 参数重写为模型名称部分。举例来说,原生的 LLM 请求体是: + +```json +{ + "model": "qwen/qwen-long", + "frequency_penalty": 0, + "max_tokens": 800, + "stream": false, + "messages": [{ + "role": "user", + "content": "higress项目主仓库的github地址是什么" + }], + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 +} +``` + +经过这个插件后,将添加下面这个请求头(可以用于路由匹配): + +x-higress-llm-provider: qwen + +原始的 LLM 请求体将被改成: + +```json +{ + "model": "qwen-long", + "frequency_penalty": 0, + "max_tokens": 800, + "stream": false, + "messages": [{ + "role": "user", + "content": "higress项目主仓库的github地址是什么" + }], + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 +} +``` diff --git a/plugins/wasm-cpp/extensions/model_router/README_EN.md b/plugins/wasm-cpp/extensions/model_router/README_EN.md new file mode 100644 index 0000000000..4d2eaf1fee --- /dev/null +++ b/plugins/wasm-cpp/extensions/model_router/README_EN.md @@ -0,0 +1,63 @@ +## Function Description +The `model-router` plugin implements the functionality of routing based on the `model` parameter in the LLM protocol. + +## Runtime Properties + +Plugin Execution Phase: `Default Phase` +Plugin Execution Priority: `260` + +## Configuration Fields + +| Name | Data Type | Filling Requirement | Default Value | Description | +| -------------------- | ------------- | --------------------- | ---------------------- | ----------------------------------------------------- | +| `enable` | bool | Optional | false | Whether to enable routing based on the `model` parameter | +| `model_key` | string | Optional | model | The location of the `model` parameter in the request body | +| `add_header_key` | string | Optional | x-higress-llm-provider | The header where the parsed provider name from the `model` parameter will be placed | + +## Effect Description + +To enable routing based on the `model` parameter, use the following configuration: + +```yaml +enable: true +``` + +After enabling, the plugin extracts the provider part (if any) from the `model` parameter in the request, and sets it in the `x-higress-llm-provider` request header for subsequent routing. It also rewrites the `model` parameter to the model name part. For example, the original LLM request body is: + +```json +{ + "model": "openai/gpt-4o", + "frequency_penalty": 0, + "max_tokens": 800, + "stream": false, + "messages": [{ + "role": "user", + "content": "What is the GitHub address for the main repository of the Higress project?" + }], + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 +} +``` + +After processing by the plugin, the following request header (which can be used for routing matching) will be added: + +`x-higress-llm-provider: openai` + +The original LLM request body will be modified to: + +```json +{ + "model": "gpt-4o", + "frequency_penalty": 0, + "max_tokens": 800, + "stream": false, + "messages": [{ + "role": "user", + "content": "What is the GitHub address for the main repository of the Higress project?" + }], + "presence_penalty": 0, + "temperature": 0.7, + "top_p": 0.95 +} +``` diff --git a/plugins/wasm-cpp/extensions/model_router/plugin.cc b/plugins/wasm-cpp/extensions/model_router/plugin.cc new file mode 100644 index 0000000000..457864d268 --- /dev/null +++ b/plugins/wasm-cpp/extensions/model_router/plugin.cc @@ -0,0 +1,189 @@ +// 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. + +#include "extensions/model_router/plugin.h" + +#include +#include + +#include "absl/strings/str_cat.h" +#include "absl/strings/str_split.h" +#include "common/http_util.h" +#include "common/json_util.h" + +using ::nlohmann::json; +using ::Wasm::Common::JsonArrayIterate; +using ::Wasm::Common::JsonGetField; +using ::Wasm::Common::JsonObjectIterate; +using ::Wasm::Common::JsonValueAs; + +#ifdef NULL_PLUGIN + +namespace proxy_wasm { +namespace null_plugin { +namespace model_router { + +PROXY_WASM_NULL_PLUGIN_REGISTRY + +#endif + +static RegisterContextFactory register_ModelRouter( + CONTEXT_FACTORY(PluginContext), ROOT_FACTORY(PluginRootContext)); + +namespace { + +constexpr std::string_view SetDecoderBufferLimitKey = + "SetRequestBodyBufferLimit"; +constexpr std::string_view DefaultMaxBodyBytes = "10485760"; + +} // namespace + +bool PluginRootContext::parsePluginConfig(const json& configuration, + ModelRouterConfigRule& rule) { + if (auto it = configuration.find("enable"); it != configuration.end()) { + if (it->is_boolean()) { + rule.enable_ = it->get(); + } else { + LOG_WARN("Invalid type for enable. Expected boolean."); + return false; + } + } + + if (auto it = configuration.find("model_key"); it != configuration.end()) { + if (it->is_string()) { + rule.model_key_ = it->get(); + } else { + LOG_WARN("Invalid type for model_key. Expected string."); + return false; + } + } + + if (auto it = configuration.find("add_header_key"); + it != configuration.end()) { + if (it->is_string()) { + rule.add_header_key_ = it->get(); + } else { + LOG_WARN("Invalid type for add_header_key. Expected string."); + return false; + } + } + + return true; +} + +bool PluginRootContext::onConfigure(size_t size) { + // Parse configuration JSON string. + if (size > 0 && !configure(size)) { + LOG_WARN("configuration has errors initialization will not continue."); + return false; + } + return true; +} + +bool PluginRootContext::configure(size_t configuration_size) { + auto configuration_data = getBufferBytes(WasmBufferType::PluginConfiguration, + 0, configuration_size); + // Parse configuration JSON string. + auto result = ::Wasm::Common::JsonParse(configuration_data->view()); + if (!result) { + LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", + configuration_data->view())); + return false; + } + if (!parseAuthRuleConfig(result.value())) { + LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", + configuration_data->view())); + return false; + } + return true; +} + +FilterHeadersStatus PluginRootContext::onHeader( + const ModelRouterConfigRule& rule) { + if (!rule.enable_ || !Wasm::Common::Http::hasRequestBody()) { + return FilterHeadersStatus::Continue; + } + auto content_type_value = + getRequestHeader(Wasm::Common::Http::Header::ContentType); + if (!absl::StrContains(content_type_value->view(), + Wasm::Common::Http::ContentTypeValues::Json)) { + return FilterHeadersStatus::Continue; + } + removeRequestHeader(Wasm::Common::Http::Header::ContentLength); + setFilterState(SetDecoderBufferLimitKey, DefaultMaxBodyBytes); + return FilterHeadersStatus::StopIteration; +} + +FilterDataStatus PluginRootContext::onBody(const ModelRouterConfigRule& rule, + std::string_view body) { + const auto& model_key = rule.model_key_; + const auto& add_header_key = rule.add_header_key_; + auto body_json_opt = ::Wasm::Common::JsonParse(body); + if (!body_json_opt) { + LOG_WARN(absl::StrCat("cannot parse body to JSON string: ", body)); + return FilterDataStatus::Continue; + } + auto body_json = body_json_opt.value(); + if (body_json.contains(model_key)) { + std::string model_value = body_json[model_key]; + auto pos = model_value.find('/'); + if (pos != std::string::npos) { + const auto& provider = model_value.substr(0, pos); + const auto& model = model_value.substr(pos + 1); + replaceRequestHeader(add_header_key, provider); + body_json[model_key] = model; + setBuffer(WasmBufferType::HttpRequestBody, 0, + std::numeric_limits::max(), body_json.dump()); + LOG_DEBUG(absl::StrCat("model route to provider:", provider, + ", model:", model)); + } else { + LOG_DEBUG(absl::StrCat("model route not work, model:", model_value)); + } + } + return FilterDataStatus::Continue; +} + +FilterHeadersStatus PluginContext::onRequestHeaders(uint32_t, bool) { + auto* rootCtx = rootContext(); + return rootCtx->onHeaders([rootCtx, this](const auto& config) { + auto ret = rootCtx->onHeader(config); + if (ret == FilterHeadersStatus::StopIteration) { + this->config_ = &config; + } + return ret; + }); +} + +FilterDataStatus PluginContext::onRequestBody(size_t body_size, + bool end_stream) { + if (config_ == nullptr) { + return FilterDataStatus::Continue; + } + body_total_size_ += body_size; + if (!end_stream) { + return FilterDataStatus::StopIterationAndBuffer; + } + auto body = + getBufferBytes(WasmBufferType::HttpRequestBody, 0, body_total_size_); + auto* rootCtx = rootContext(); + return rootCtx->onBody(*config_, body->view()); +} + +#ifdef NULL_PLUGIN + +} // namespace model_router +} // namespace null_plugin +} // namespace proxy_wasm + +#endif diff --git a/plugins/wasm-cpp/extensions/model_router/plugin.h b/plugins/wasm-cpp/extensions/model_router/plugin.h new file mode 100644 index 0000000000..16cfdf8509 --- /dev/null +++ b/plugins/wasm-cpp/extensions/model_router/plugin.h @@ -0,0 +1,85 @@ +/* + * 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. + */ + +#include + +#include +#include + +#include "common/route_rule_matcher.h" +#define ASSERT(_X) assert(_X) + +#ifndef NULL_PLUGIN + +#include "proxy_wasm_intrinsics.h" + +#else + +#include "include/proxy-wasm/null_plugin.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace model_router { + +#endif + +struct ModelRouterConfigRule { + bool enable_ = false; + std::string model_key_ = "model"; + std::string add_header_key_ = "x-higress-llm-provider"; +}; + +// PluginRootContext is the root context for all streams processed by the +// thread. It has the same lifetime as the worker thread and acts as target for +// interactions that outlives individual stream, e.g. timer, async calls. +class PluginRootContext : public RootContext, + public RouteRuleMatcher { + public: + PluginRootContext(uint32_t id, std::string_view root_id) + : RootContext(id, root_id) {} + ~PluginRootContext() {} + bool onConfigure(size_t) override; + FilterHeadersStatus onHeader(const ModelRouterConfigRule&); + FilterDataStatus onBody(const ModelRouterConfigRule&, std::string_view); + bool configure(size_t); + + private: + bool parsePluginConfig(const json&, ModelRouterConfigRule&) override; +}; + +// Per-stream context. +class PluginContext : public Context { + public: + explicit PluginContext(uint32_t id, RootContext* root) : Context(id, root) {} + FilterHeadersStatus onRequestHeaders(uint32_t, bool) override; + FilterDataStatus onRequestBody(size_t, bool) override; + + private: + inline PluginRootContext* rootContext() { + return dynamic_cast(this->root()); + } + + size_t body_total_size_ = 0; + const ModelRouterConfigRule* config_ = nullptr; +}; + +#ifdef NULL_PLUGIN + +} // namespace model_router +} // namespace null_plugin +} // namespace proxy_wasm + +#endif diff --git a/plugins/wasm-cpp/extensions/model_router/plugin_test.cc b/plugins/wasm-cpp/extensions/model_router/plugin_test.cc new file mode 100644 index 0000000000..9ce5998051 --- /dev/null +++ b/plugins/wasm-cpp/extensions/model_router/plugin_test.cc @@ -0,0 +1,144 @@ +// 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. + +#include "extensions/model_router/plugin.h" + +#include + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "include/proxy-wasm/context.h" +#include "include/proxy-wasm/null.h" + +namespace proxy_wasm { +namespace null_plugin { +namespace model_router { + +NullPluginRegistry* context_registry_; +RegisterNullVmPluginFactory register_model_router_plugin("model_router", []() { + return std::make_unique(model_router::context_registry_); +}); + +class MockContext : public proxy_wasm::ContextBase { + public: + MockContext(WasmBase* wasm) : ContextBase(wasm) {} + MOCK_METHOD(BufferInterface*, getBuffer, (WasmBufferType)); + MOCK_METHOD(WasmResult, log, (uint32_t, std::string_view)); + MOCK_METHOD(WasmResult, setBuffer, + (WasmBufferType, size_t, size_t, std::string_view)); + MOCK_METHOD(WasmResult, getHeaderMapValue, + (WasmHeaderMapType /* type */, std::string_view /* key */, + std::string_view* /*result */)); + MOCK_METHOD(WasmResult, addHeaderMapValue, + (WasmHeaderMapType, std::string_view, std::string_view)); + MOCK_METHOD(WasmResult, getProperty, (std::string_view, std::string*)); + MOCK_METHOD(WasmResult, setProperty, (std::string_view, std::string_view)); +}; +class ModelRouterTest : public ::testing::Test { + protected: + ModelRouterTest() { + // Initialize test VM + test_vm_ = createNullVm(); + wasm_base_ = std::make_unique( + std::move(test_vm_), "test-vm", "", "", + std::unordered_map{}, + AllowedCapabilitiesMap{}); + wasm_base_->load("model_router"); + wasm_base_->initialize(); + // Initialize host side context + mock_context_ = std::make_unique(wasm_base_.get()); + current_context_ = mock_context_.get(); + // Initialize Wasm sandbox context + root_context_ = std::make_unique(0, ""); + context_ = std::make_unique(1, root_context_.get()); + + ON_CALL(*mock_context_, log(testing::_, testing::_)) + .WillByDefault([](uint32_t, std::string_view m) { + std::cerr << m << "\n"; + return WasmResult::Ok; + }); + + ON_CALL(*mock_context_, getBuffer(testing::_)) + .WillByDefault([&](WasmBufferType type) { + if (type == WasmBufferType::HttpRequestBody) { + return &body_; + } + return &config_; + }); + ON_CALL(*mock_context_, getHeaderMapValue(WasmHeaderMapType::RequestHeaders, + testing::_, testing::_)) + .WillByDefault([&](WasmHeaderMapType, std::string_view header, + std::string_view* result) { + if (header == "content-type") { + *result = "application/json"; + } else if (header == "content-length") { + *result = "1024"; + } + return WasmResult::Ok; + }); + ON_CALL(*mock_context_, addHeaderMapValue(WasmHeaderMapType::RequestHeaders, + testing::_, testing::_)) + .WillByDefault([&](WasmHeaderMapType, std::string_view header, + std::string_view value) { return WasmResult::Ok; }); + ON_CALL(*mock_context_, getProperty(testing::_, testing::_)) + .WillByDefault([&](std::string_view path, std::string* result) { + *result = route_name_; + return WasmResult::Ok; + }); + ON_CALL(*mock_context_, setProperty(testing::_, testing::_)) + .WillByDefault( + [&](std::string_view, std::string_view) { return WasmResult::Ok; }); + } + ~ModelRouterTest() override {} + std::unique_ptr wasm_base_; + std::unique_ptr test_vm_; + std::unique_ptr mock_context_; + std::unique_ptr root_context_; + std::unique_ptr context_; + std::string route_name_; + BufferBase body_; + BufferBase config_; +}; + +TEST_F(ModelRouterTest, RewriteModelAndHeader) { + std::string configuration = R"( +{ + "enable": true + })"; + + config_.set(configuration); + EXPECT_TRUE(root_context_->configure(configuration.size())); + + std::string request_json = R"({"model": "qwen/qwen-long"})"; + EXPECT_CALL(*mock_context_, + setBuffer(testing::_, testing::_, testing::_, testing::_)) + .WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) { + EXPECT_EQ(body, R"({"model":"qwen-long"})"); + return WasmResult::Ok; + }); + + EXPECT_CALL( + *mock_context_, + addHeaderMapValue(testing::_, std::string_view("x-higress-llm-provider"), + std::string_view("qwen"))); + + body_.set(request_json); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + EXPECT_EQ(context_->onRequestBody(28, true), FilterDataStatus::Continue); +} + +} // namespace model_router +} // namespace null_plugin +} // namespace proxy_wasm From 871ae179c3b716943a5c724668283c65a90c879f Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Tue, 22 Oct 2024 21:39:01 +0800 Subject: [PATCH 21/64] Ai data masking fix (#1420) --- plugins/README.md | 1 + .../extensions/ai-data-masking/src/lib.rs | 9 ++++++--- plugins/wasm-rust/src/plugin_wrapper.rs | 19 ++++++++++++++++++- plugins/wasm-rust/src/request_wrapper.rs | 2 +- 4 files changed, 26 insertions(+), 5 deletions(-) diff --git a/plugins/README.md b/plugins/README.md index dc2ac8b1ca..7cdabde1d8 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -1,5 +1,6 @@ ## Wasm 插件 + 目前 Higress 提供了 c++ 和 golang 两种 Wasm 插件开发框架,支持 Wasm 插件路由&域名级匹配生效。 同时提供了多个内置插件,用户可以基于 Higress 提供的官方镜像仓库直接使用这些插件(以 c++ 版本举例): diff --git a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs index 01573585ac..99d0144e97 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs +++ b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs @@ -16,6 +16,7 @@ use fancy_regex::Regex; use grok::patterns; use higress_wasm_rust::log::Log; use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper}; +use higress_wasm_rust::request_wrapper::has_request_body; use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher}; use jieba_rs::Jieba; use jsonpath_rust::{JsonPath, JsonPathValue}; @@ -519,7 +520,11 @@ impl HttpContext for AiDataMasking { _num_headers: usize, _end_of_stream: bool, ) -> HeaderAction { - HeaderAction::StopIteration + if has_request_body() { + HeaderAction::StopIteration + } else { + HeaderAction::Continue + } } fn on_http_response_headers( &mut self, @@ -669,14 +674,12 @@ impl HttpContextWrapper for AiDataMasking { } fn on_http_response_complete_body(&mut self, res_body: &Bytes) -> DataAction { if self.config.is_none() { - self.reset_http_response(); return DataAction::Continue; } let config = self.config.as_ref().unwrap(); let mut res_body = match String::from_utf8(res_body.clone()) { Ok(r) => r, Err(_) => { - self.reset_http_response(); return DataAction::Continue; } }; diff --git a/plugins/wasm-rust/src/plugin_wrapper.rs b/plugins/wasm-rust/src/plugin_wrapper.rs index 5a00c0bda4..abe188a99f 100644 --- a/plugins/wasm-rust/src/plugin_wrapper.rs +++ b/plugins/wasm-rust/src/plugin_wrapper.rs @@ -309,7 +309,9 @@ where fn on_http_request_headers(&mut self, num_headers: usize, end_of_stream: bool) -> HeaderAction { let binding = self.rule_matcher.borrow(); self.config = binding.get_match_config().map(|config| config.1.clone()); - + if self.config.is_none() { + return HeaderAction::Continue; + } for (k, v) in self.get_http_request_headers_bytes() { match String::from_utf8(v) { Ok(header_value) => { @@ -340,6 +342,9 @@ where } fn on_http_request_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction { + if self.config.is_none() { + return DataAction::Continue; + } if !self.http_content.borrow().cache_request_body() { return self .http_content @@ -362,6 +367,9 @@ where } fn on_http_request_trailers(&mut self, num_trailers: usize) -> Action { + if self.config.is_none() { + return Action::Continue; + } self.http_content .borrow_mut() .on_http_request_trailers(num_trailers) @@ -372,6 +380,9 @@ where num_headers: usize, end_of_stream: bool, ) -> HeaderAction { + if self.config.is_none() { + return HeaderAction::Continue; + } for (k, v) in self.get_http_response_headers_bytes() { match String::from_utf8(v) { Ok(header_value) => { @@ -399,6 +410,9 @@ where } fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction { + if self.config.is_none() { + return DataAction::Continue; + } if !self.http_content.borrow().cache_response_body() { return self .http_content @@ -423,6 +437,9 @@ where } fn on_http_response_trailers(&mut self, num_trailers: usize) -> Action { + if self.config.is_none() { + return Action::Continue; + } self.http_content .borrow_mut() .on_http_response_trailers(num_trailers) diff --git a/plugins/wasm-rust/src/request_wrapper.rs b/plugins/wasm-rust/src/request_wrapper.rs index bc9624f6a9..c9a997456c 100644 --- a/plugins/wasm-rust/src/request_wrapper.rs +++ b/plugins/wasm-rust/src/request_wrapper.rs @@ -68,7 +68,7 @@ pub fn has_request_body() -> bool { content_type, content_length_str, transfer_encoding ) ).unwrap(); - if !content_type.is_some_and(|x| !x.is_empty()) { + if content_type.is_some_and(|x| !x.is_empty()) { return true; } if let Some(cl) = content_length_str { From 0d79386ce2e10d5ead08f7384773bde7557b7bda Mon Sep 17 00:00:00 2001 From: Bingkun Zhao <49975170+sjtuzbk@users.noreply.github.com> Date: Tue, 22 Oct 2024 22:17:56 +0800 Subject: [PATCH 22/64] fix a bug of ip-restriction plugin (#1422) --- plugins/wasm-go/extensions/ip-restriction/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/wasm-go/extensions/ip-restriction/main.go b/plugins/wasm-go/extensions/ip-restriction/main.go index bd2d1eb61b..7752639813 100644 --- a/plugins/wasm-go/extensions/ip-restriction/main.go +++ b/plugins/wasm-go/extensions/ip-restriction/main.go @@ -60,7 +60,7 @@ func parseConfig(json gjson.Result, config *RestrictionConfig, log wrapper.Log) } status := json.Get("status") if status.Exists() && status.Uint() > 1 { - config.Status = uint32(header.Uint()) + config.Status = uint32(status.Uint()) } else { config.Status = DefaultDenyStatus } From b8f5826a32d07c98c39a5c902218c72f0c23227d Mon Sep 17 00:00:00 2001 From: fengxsong Date: Wed, 23 Oct 2024 09:03:07 +0800 Subject: [PATCH 23/64] fix: do not create ingressclass when it's empty (#1419) Signed-off-by: fengxusong --- helm/core/templates/ingressclass.yaml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/helm/core/templates/ingressclass.yaml b/helm/core/templates/ingressclass.yaml index 000e1117e3..47caa33eb3 100644 --- a/helm/core/templates/ingressclass.yaml +++ b/helm/core/templates/ingressclass.yaml @@ -1,6 +1,8 @@ +{{- if .Values.global.ingressClass }} apiVersion: networking.k8s.io/v1 kind: IngressClass metadata: name: {{ .Values.global.ingressClass }} spec: - controller: higress.io/higress-controller \ No newline at end of file + controller: higress.io/higress-controller +{{- end }} From bb6c43c767d129f83d9799413bb503ed86ec0713 Mon Sep 17 00:00:00 2001 From: mamba <371510756@qq.com> Date: Wed, 23 Oct 2024 09:34:00 +0800 Subject: [PATCH 24/64] =?UTF-8?q?feat:=20=E3=80=90frontend-gray=E3=80=91?= =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20skipedRoutes=E4=BB=A5=E5=8F=8AskipedByHead?= =?UTF-8?q?ers=20=E9=85=8D=E7=BD=AE=20(#1409)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Kent Dong --- .../extensions/frontend-gray/README.md | 5 +- .../extensions/frontend-gray/config/config.go | 5 ++ .../extensions/frontend-gray/envoy.yaml | 3 + .../wasm-go/extensions/frontend-gray/main.go | 32 +++++--- .../extensions/frontend-gray/util/utils.go | 81 ++++++++++++++----- .../frontend-gray/util/utils_test.go | 20 +++-- 6 files changed, 101 insertions(+), 45 deletions(-) diff --git a/plugins/wasm-go/extensions/frontend-gray/README.md b/plugins/wasm-go/extensions/frontend-gray/README.md index dba4b73c0f..870100eb2f 100644 --- a/plugins/wasm-go/extensions/frontend-gray/README.md +++ b/plugins/wasm-go/extensions/frontend-gray/README.md @@ -20,6 +20,9 @@ description: 前端灰度插件配置参考 | `localStorageGrayKey` | string | 非必填 | - | 使用JWT鉴权方式,用户ID的唯一标识来自`localStorage`中,如果配置了当前参数,则`grayKey`失效 | | `graySubKey` | string | 非必填 | - | 用户身份信息可能以JSON形式透出,比如:`userInfo:{ userCode:"001" }`,当前例子`graySubKey`取值为`userCode` | | `userStickyMaxAge` | int | 非必填 | 172800 | 用户粘滞的时长:单位为秒,默认为`172800`,2天时间 | +| `skippedPathPrefixes` | array of strings | 非必填 | - | 用于排除特定路径,避免当前插件处理这些请求。例如,在 rewrite 场景下,XHR 接口请求 `/api/xxx` 如果经过插件转发逻辑,可能会导致非预期的结果。 | +| `skippedByHeaders` | map of string to string | 非必填 | - | 用于通过请求头过滤,指定哪些请求不被当前插件 +处理。`skippedPathPrefixes` 的优先级高于当前配置,且页面HTML请求不受本配置的影响。若本配置为空,默认会判断`sec-fetch-mode=cors`以及`upgrade=websocket`两个header头,进行过滤 | | `rules` | array of object | 必填 | - | 用户定义不同的灰度规则,适配不同的灰度场景 | | `rewrite` | object | 必填 | - | 重写配置,一般用于OSS/CDN前端部署的重写配置 | | `baseDeployment` | object | 非必填 | - | 配置Base基线规则的配置 | @@ -266,4 +269,4 @@ injection: - - ``` -通过 `injection`往HTML首页注入代码,可以在`head`标签注入代码,也可以在`body`标签的`first`和`last`位置注入代码。 \ No newline at end of file +通过 `injection`往HTML首页注入代码,可以在`head`标签注入代码,也可以在`body`标签的`first`和`last`位置注入代码。 diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config.go b/plugins/wasm-go/extensions/frontend-gray/config/config.go index ecfbb3a836..25596fefef 100644 --- a/plugins/wasm-go/extensions/frontend-gray/config/config.go +++ b/plugins/wasm-go/extensions/frontend-gray/config/config.go @@ -12,6 +12,7 @@ const ( XPreHigressTag = "x-pre-higress-tag" IsPageRequest = "is-page-request" IsNotFound = "is-not-found" + EnabledGray = "enabled-gray" ) type LogInfo func(format string, args ...interface{}) @@ -61,6 +62,8 @@ type GrayConfig struct { GrayDeployments []*Deployment BackendGrayTag string Injection *Injection + SkippedPathPrefixes []string + SkippedByHeaders map[string]string } func convertToStringList(results []gjson.Result) []string { @@ -91,6 +94,8 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { grayConfig.BackendGrayTag = json.Get("backendGrayTag").String() grayConfig.UserStickyMaxAge = json.Get("userStickyMaxAge").String() grayConfig.Html = json.Get("html").String() + grayConfig.SkippedPathPrefixes = convertToStringList(json.Get("skippedPathPrefixes").Array()) + grayConfig.SkippedByHeaders = convertToStringMap(json.Get("skippedByHeaders")) if grayConfig.UserStickyMaxAge == "" { // 默认值2天 diff --git a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml index 239e221bd7..bc584d5ccd 100644 --- a/plugins/wasm-go/extensions/frontend-gray/envoy.yaml +++ b/plugins/wasm-go/extensions/frontend-gray/envoy.yaml @@ -82,6 +82,9 @@ static_resources: "/app1": "/mfe/app1/{version}" } }, + "skippedPathPrefixes": [ + "/api/" + ], "baseDeployment": { "version": "dev" }, diff --git a/plugins/wasm-go/extensions/frontend-gray/main.go b/plugins/wasm-go/extensions/frontend-gray/main.go index b1b5d28aff..c5e738eeac 100644 --- a/plugins/wasm-go/extensions/frontend-gray/main.go +++ b/plugins/wasm-go/extensions/frontend-gray/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "net/http" + "path" "strings" "github.com/alibaba/higress/plugins/wasm-go/extensions/frontend-gray/config" @@ -32,15 +33,18 @@ func parseConfig(json gjson.Result, grayConfig *config.GrayConfig, log wrapper.L } func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log wrapper.Log) types.Action { - if !util.IsGrayEnabled(grayConfig) { + requestPath, _ := proxywasm.GetHttpRequestHeader(":path") + requestPath = path.Clean(requestPath) + enabledGray := util.IsGrayEnabled(grayConfig, requestPath) + ctx.SetContext(config.EnabledGray, enabledGray) + + if !enabledGray { + ctx.DontReadRequestBody() return types.ActionContinue } cookies, _ := proxywasm.GetHttpRequestHeader("cookie") - path, _ := proxywasm.GetHttpRequestHeader(":path") - fetchMode, _ := proxywasm.GetHttpRequestHeader("sec-fetch-mode") - - isPageRequest := util.IsPageRequest(fetchMode, path) + isPageRequest := util.IsPageRequest(requestPath) hasRewrite := len(grayConfig.Rewrite.File) > 0 || len(grayConfig.Rewrite.Index) > 0 grayKeyValueByCookie := util.ExtractCookieValueByKey(cookies, grayConfig.GrayKey) grayKeyValueByHeader, _ := proxywasm.GetHttpRequestHeader(grayConfig.GrayKey) @@ -73,7 +77,7 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } else { deployment = util.FilterGrayRule(&grayConfig, grayKeyValue) } - log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, path, deployment.BackendVersion, preVersion, preUniqueClientId) + log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId) } else { grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue) deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest) @@ -91,14 +95,14 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } if hasRewrite { - rewritePath := path + rewritePath := requestPath if isPageRequest { - rewritePath = util.IndexRewrite(path, deployment.Version, grayConfig.Rewrite.Index) + rewritePath = util.IndexRewrite(requestPath, deployment.Version, grayConfig.Rewrite.Index) } else { - rewritePath = util.PrefixFileRewrite(path, deployment.Version, grayConfig.Rewrite.File) + rewritePath = util.PrefixFileRewrite(requestPath, deployment.Version, grayConfig.Rewrite.File) } - if path != rewritePath { - log.Infof("rewrite path:%s, rewritePath:%s, Version:%v", path, rewritePath, deployment.Version) + if requestPath != rewritePath { + log.Infof("rewrite path:%s, rewritePath:%s, Version:%v", requestPath, rewritePath, deployment.Version) proxywasm.ReplaceHttpRequestHeader(":path", rewritePath) } } @@ -106,7 +110,8 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log wrapper.Log) types.Action { - if !util.IsGrayEnabled(grayConfig) { + enabledGray, _ := ctx.GetContext(config.EnabledGray).(bool) + if !enabledGray { ctx.DontReadResponseBody() return types.ActionContinue } @@ -179,7 +184,8 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, body []byte, log wrapper.Log) types.Action { - if !util.IsGrayEnabled(grayConfig) { + enabledGray, _ := ctx.GetContext(config.EnabledGray).(bool) + if !enabledGray { return types.ActionContinue } isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool) diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils.go b/plugins/wasm-go/extensions/frontend-gray/util/utils.go index e67d3e0893..da93c68212 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils.go @@ -47,7 +47,39 @@ func GetRealIpFromXff(xff string) string { return "" } -func IsGrayEnabled(grayConfig config.GrayConfig) bool { +func IsRequestSkippedByHeaders(grayConfig config.GrayConfig) bool { + secFetchMode, _ := proxywasm.GetHttpRequestHeader("sec-fetch-mode") + upgrade, _ := proxywasm.GetHttpRequestHeader("upgrade") + if len(grayConfig.SkippedByHeaders) == 0 { + // 默认不走插件逻辑的header + return secFetchMode == "cors" || upgrade == "websocket" + } + for headerKey, headerValue := range grayConfig.SkippedByHeaders { + requestHeader, _ := proxywasm.GetHttpRequestHeader(headerKey) + if requestHeader == headerValue { + return true + } + } + return false +} + +func IsGrayEnabled(grayConfig config.GrayConfig, requestPath string) bool { + // 当前路径中前缀为 SkipedRoute,则不走插件逻辑 + for _, prefix := range grayConfig.SkippedPathPrefixes { + if strings.HasPrefix(requestPath, prefix) { + return false + } + } + + // 如果是首页,进入插件逻辑 + if IsPageRequest(requestPath) { + return true + } + // 检查header标识,判断是否需要跳过 + if IsRequestSkippedByHeaders(grayConfig) { + return false + } + // 检查是否存在重写主机 if grayConfig.Rewrite != nil && grayConfig.Rewrite.Host != "" { return true @@ -132,29 +164,35 @@ var indexSuffixes = []string{ ".html", ".htm", ".jsp", ".php", ".asp", ".aspx", ".erb", ".ejs", ".twig", } -func IsPageRequest(fetchMode string, myPath string) bool { - if fetchMode == "cors" { - return false +func IsPageRequest(requestPath string) bool { + if requestPath == "/" || requestPath == "" { + return true } - ext := path.Ext(myPath) + ext := path.Ext(requestPath) return ext == "" || ContainsValue(indexSuffixes, ext) } -// 首页Rewrite -func IndexRewrite(path, version string, matchRules map[string]string) string { - // Create a slice of keys in matchRules and sort them by length in descending order +// SortKeysByLengthAndLexicographically 按长度降序和字典序排序键 +func SortKeysByLengthAndLexicographically(matchRules map[string]string) []string { keys := make([]string, 0, len(matchRules)) for prefix := range matchRules { keys = append(keys, prefix) } sort.Slice(keys, func(i, j int) bool { if len(keys[i]) != len(keys[j]) { - return len(keys[i]) > len(keys[j]) // Sort by length + return len(keys[i]) > len(keys[j]) // 按长度排序 } - return keys[i] < keys[j] // Sort lexicographically + return keys[i] < keys[j] // 按字典序排序 }) + return keys +} - // Iterate over sorted keys to find the longest match +// 首页Rewrite +func IndexRewrite(path, version string, matchRules map[string]string) string { + // 使用新的排序函数 + keys := SortKeysByLengthAndLexicographically(matchRules) + + // 遍历排序后的键以找到最长匹配 for _, prefix := range keys { if strings.HasPrefix(path, prefix) { rewrite := matchRules[prefix] @@ -166,18 +204,21 @@ func IndexRewrite(path, version string, matchRules map[string]string) string { } func PrefixFileRewrite(path, version string, matchRules map[string]string) string { - var matchedPrefix, replacement string - for prefix, template := range matchRules { + // 对规则的键进行排序 + sortedKeys := SortKeysByLengthAndLexicographically(matchRules) + + // 遍历排序后的键 + for _, prefix := range sortedKeys { if strings.HasPrefix(path, prefix) { - if len(prefix) > len(matchedPrefix) { // 找到更长的前缀 - matchedPrefix = prefix - replacement = strings.Replace(template, "{version}", version, 1) - } + // 找到第一个匹配的前缀就停止,因为它是最长的匹配 + replacement := strings.Replace(matchRules[prefix], "{version}", version, 1) + newPath := strings.Replace(path, prefix, replacement+"/", 1) + return filepath.Clean(newPath) } } - // 将path 中的前缀部分用 replacement 替换掉 - newPath := strings.Replace(path, matchedPrefix, replacement+"/", 1) - return filepath.Clean(newPath) + + // 如果没有匹配,返回原始路径 + return path } func GetVersion(grayConfig config.GrayConfig, deployment *config.Deployment, xPreHigressVersion string, isPageRequest bool) *config.Deployment { diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go index b6681c98af..5fb69bb364 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils_test.go @@ -108,22 +108,20 @@ func TestPrefixFileRewrite(t *testing.T) { func TestIsPageRequest(t *testing.T) { var tests = []struct { - fetchMode string - p string - output bool + p string + output bool }{ - {"cors", "/js/a.js", false}, - {"no-cors", "/js/a.js", false}, - {"no-cors", "/images/a.png", false}, - {"no-cors", "/index", true}, - {"cors", "/inde", false}, - {"no-cors", "/index.html", true}, - {"no-cors", "/demo.php", true}, + {"/js/a.js", false}, + {"/js/a.js", false}, + {"/images/a.png", false}, + {"/index", true}, + {"/index.html", true}, + {"/demo.php", true}, } for _, test := range tests { testPath := test.p t.Run(testPath, func(t *testing.T) { - output := IsPageRequest(test.fetchMode, testPath) + output := IsPageRequest(testPath) assert.Equal(t, test.output, output) }) } From d76f574ab303950080aff800e3361cef7b9de6eb Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Wed, 23 Oct 2024 13:19:02 +0800 Subject: [PATCH 25/64] plugin ai-data-mask add log (#1423) --- .../extensions/ai-data-masking/README.md | 2 +- .../extensions/ai-data-masking/README_EN.md | 2 +- .../extensions/ai-data-masking/src/lib.rs | 37 +++++++++++++++---- 3 files changed, 31 insertions(+), 10 deletions(-) diff --git a/plugins/wasm-rust/extensions/ai-data-masking/README.md b/plugins/wasm-rust/extensions/ai-data-masking/README.md index 185d559d13..b892b3dab8 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/README.md +++ b/plugins/wasm-rust/extensions/ai-data-masking/README.md @@ -36,7 +36,7 @@ description: AI 数据脱敏插件配置参考 | deny_openai | bool | true | 对openai协议进行拦截 | | deny_jsonpath | string | [] | 对指定jsonpath拦截 | | deny_raw | bool | false | 对原始body拦截 | -| system_deny | bool | true | 开启内置拦截规则 | +| system_deny | bool | false | 开启内置拦截规则 | | deny_code | int | 200 | 拦截时http状态码 | | deny_message | string | 提问或回答中包含敏感词,已被屏蔽 | 拦截时ai返回消息 | | deny_raw_message | string | {"errmsg":"提问或回答中包含敏感词,已被屏蔽"} | 非openai拦截时返回内容 | diff --git a/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md b/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md index 6b03e2122a..45e1622875 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md +++ b/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md @@ -31,7 +31,7 @@ Plugin Execution Priority: `991` | deny_openai | bool | true | Intercept openai protocol | | deny_jsonpath | string | [] | Intercept specified jsonpath | | deny_raw | bool | false | Intercept raw body | -| system_deny | bool | true | Enable built-in interception rules | +| system_deny | bool | false | Enable built-in interception rules | | deny_code | int | 200 | HTTP status code when intercepted | | deny_message | string | Sensitive words found in the question or answer have been blocked | AI returned message when intercepted | | deny_raw_message | string | {"errmsg":"Sensitive words found in the question or answer have been blocked"} | Content returned when not openai intercepted | diff --git a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs index 99d0144e97..89c0da5219 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs +++ b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs @@ -71,6 +71,7 @@ struct AiDataMasking { is_openai: bool, stream: bool, res_body: Bytes, + log: Log, } fn deserialize_regexp<'de, D>(deserializer: D) -> Result where @@ -159,7 +160,7 @@ fn default_deny_raw() -> bool { false } fn default_system_deny() -> bool { - true + false } fn default_deny_code() -> u16 { 200 @@ -257,13 +258,13 @@ impl DenyWord { DenyWord::empty() } - fn check(&self, message: &str) -> bool { + fn check(&self, message: &str) -> Option { for word in self.jieba.cut(message, true) { if self.words.contains(word) { - return true; + return Some(word.to_string()); } } - false + None } } impl System { @@ -377,17 +378,30 @@ impl RootContextWrapper for AiDataMaskingRoot { is_openai: false, stream: false, res_body: Bytes::new(), + log: Log::new(PLUGIN_NAME.to_string()), })) } } impl AiDataMasking { fn check_message(&self, message: &str) -> bool { if let Some(config) = &self.config { - config.deny_words.check(message) - || (config.system_deny && SYSTEM.deny_word.check(message)) - } else { - false + if let Some(word) = config.deny_words.check(message) { + self.log().warn(&format!( + "custom deny word {} matched from {}", + word, message + )); + return true; + } else if config.system_deny { + if let Some(word) = SYSTEM.deny_word.check(message) { + self.log().warn(&format!( + "system deny word {} matched from {}", + word, message + )); + return true; + } + } } + false } fn msg_to_response(&self, msg: &str, raw_msg: &str, content_type: &str) -> (String, String) { if !self.is_openai { @@ -509,6 +523,10 @@ impl AiDataMasking { } } } + if msg != message { + self.log() + .debug(&format!("replace_request_msg from {} to {}", message, msg)); + } msg } } @@ -590,6 +608,9 @@ impl HttpContext for AiDataMasking { } } impl HttpContextWrapper for AiDataMasking { + fn log(&self) -> &Log { + &self.log + } fn on_config(&mut self, config: Rc) { self.config = Some(config.clone()); } From a5ccb90b282b841b5e56e90d99c07601ad64204c Mon Sep 17 00:00:00 2001 From: Ankur Singh Date: Thu, 24 Oct 2024 07:08:22 +0530 Subject: [PATCH 26/64] Improve the grammar of the sentence (#1426) --- CONTRIBUTING_EN.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING_EN.md b/CONTRIBUTING_EN.md index 25539cc314..30b3410838 100644 --- a/CONTRIBUTING_EN.md +++ b/CONTRIBUTING_EN.md @@ -1,6 +1,6 @@ # Contributing to Higress -It is warmly welcomed if you have interest to hack on Higress. First, we encourage this kind of willing very much. And here is a list of contributing guide for you. +Your interest in contributing to Higress is warmly welcomed. First, we encourage this kind of willing very much. And here is a list of contributing guide for you. [[中文贡献文档](./CONTRIBUTING_CN.md)] From cdd71155a9388f2c95bd1de6365c065b0d2fe479 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 24 Oct 2024 11:25:59 +0800 Subject: [PATCH 27/64] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 761f8591a8..c93bf3b15e 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ docker run -d --rm --name higress-ai -v ${PWD}:/data \ - 8080 端口:网关 HTTP 协议入口 - 8443 端口:网关 HTTPS 协议入口 -**Higress 的所有 Docker 镜像都一直使用自己独享的仓库,不受 Docker Hub 境内不可访问的影响** +**Higress 的所有 Docker 镜像都一直使用自己独享的仓库,不受 Docker Hub 境内访问受限的影响** K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start 文档](https://higress.cn/docs/latest/user/quickstart/)。 From e7561c30e55e6dddd4f2fcf12d779994b100b298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=AA=E5=8D=93=E5=BF=97?= Date: Thu, 24 Oct 2024 16:58:45 +0800 Subject: [PATCH 28/64] feat: implements text/event-stream(SSE) MIME parser (#1416) Co-authored-by: 007gzs <007gzs@gmail.com> --- plugins/wasm-rust/Makefile | 13 +- .../wasm-rust/example/sse-timing/Cargo.lock | 270 ++++++++++++++++++ .../wasm-rust/example/sse-timing/Cargo.toml | 15 + plugins/wasm-rust/example/sse-timing/Makefile | 10 + .../wasm-rust/example/sse-timing/README.md | 26 ++ .../example/sse-timing/docker-compose.yaml | 35 +++ .../wasm-rust/example/sse-timing/envoy.yaml | 76 +++++ .../wasm-rust/example/sse-timing/src/lib.rs | 198 +++++++++++++ .../example/sse-timing/sse-server/Dockerfile | 5 + .../example/sse-timing/sse-server/go.mod | 3 + .../example/sse-timing/sse-server/main.go | 42 +++ plugins/wasm-rust/src/event_stream.rs | 197 +++++++++++++ plugins/wasm-rust/src/lib.rs | 1 + 13 files changed, 885 insertions(+), 6 deletions(-) create mode 100644 plugins/wasm-rust/example/sse-timing/Cargo.lock create mode 100644 plugins/wasm-rust/example/sse-timing/Cargo.toml create mode 100644 plugins/wasm-rust/example/sse-timing/Makefile create mode 100644 plugins/wasm-rust/example/sse-timing/README.md create mode 100644 plugins/wasm-rust/example/sse-timing/docker-compose.yaml create mode 100644 plugins/wasm-rust/example/sse-timing/envoy.yaml create mode 100644 plugins/wasm-rust/example/sse-timing/src/lib.rs create mode 100644 plugins/wasm-rust/example/sse-timing/sse-server/Dockerfile create mode 100644 plugins/wasm-rust/example/sse-timing/sse-server/go.mod create mode 100644 plugins/wasm-rust/example/sse-timing/sse-server/main.go create mode 100644 plugins/wasm-rust/src/event_stream.rs diff --git a/plugins/wasm-rust/Makefile b/plugins/wasm-rust/Makefile index 25587e9698..5bc1809a38 100644 --- a/plugins/wasm-rust/Makefile +++ b/plugins/wasm-rust/Makefile @@ -6,12 +6,6 @@ IMAGE_TAG = $(if $(strip $(PLUGIN_VERSION)),${PLUGIN_VERSION},${BUILD_TIME}-${CO IMG ?= ${REGISTRY}${PLUGIN_NAME}:${IMAGE_TAG} .DEFAULT: -lint-base: - cargo fmt --all --check - cargo clippy --workspace --all-features --all-targets -lint: - cargo fmt --all --check --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml - cargo clippy --workspace --all-features --all-targets --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml build: DOCKER_BUILDKIT=1 docker build \ --build-arg PLUGIN_NAME=${PLUGIN_NAME} \ @@ -20,3 +14,10 @@ build: . @echo "" @echo "output wasm file: extensions/${PLUGIN_NAME}/plugin.wasm" + +lint-base: + cargo fmt --all --check + cargo clippy --workspace --all-features --all-targets +lint: + cargo fmt --all --check --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml + cargo clippy --workspace --all-features --all-targets --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml diff --git a/plugins/wasm-rust/example/sse-timing/Cargo.lock b/plugins/wasm-rust/example/sse-timing/Cargo.lock new file mode 100644 index 0000000000..9123a0e01c --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/Cargo.lock @@ -0,0 +1,270 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "bytes" +version = "1.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "higress-wasm-rust" +version = "0.1.0" +dependencies = [ + "downcast-rs", + "http", + "lazy_static", + "multimap", + "proxy-wasm", + "serde", + "serde_json", + "uuid", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.161" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "multimap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" +dependencies = [ + "serde", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "proc-macro2" +version = "1.0.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proxy-wasm" +version = "0.2.2" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" +dependencies = [ + "downcast-rs", + "hashbrown", + "log", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.132" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sse-timing" +version = "0.1.0" +dependencies = [ + "higress-wasm-rust", + "proxy-wasm", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "2.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/plugins/wasm-rust/example/sse-timing/Cargo.toml b/plugins/wasm-rust/example/sse-timing/Cargo.toml new file mode 100644 index 0000000000..44c4c5dfd7 --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "sse-timing" +version = "0.1.0" +edition = "2021" +publish = false + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +crate-type = ["cdylib"] + +[dependencies] +higress-wasm-rust = { path = "../../", version = "0.1.0" } +proxy-wasm = { git="https://github.com/higress-group/proxy-wasm-rust-sdk", branch="main", version="0.2.2" } +serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" diff --git a/plugins/wasm-rust/example/sse-timing/Makefile b/plugins/wasm-rust/example/sse-timing/Makefile new file mode 100644 index 0000000000..22ec19d791 --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/Makefile @@ -0,0 +1,10 @@ +BUILD_OPTS="--release" + +.DEFAULT: +build: + cargo build --target wasm32-wasi ${BUILD_OPTS} + find target -name "*.wasm" -d 3 -exec cp "{}" plugin.wasm \; + +clean: + cargo clean + rm -f plugin.wasm diff --git a/plugins/wasm-rust/example/sse-timing/README.md b/plugins/wasm-rust/example/sse-timing/README.md new file mode 100644 index 0000000000..b7d8d4bfdc --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/README.md @@ -0,0 +1,26 @@ +## Proxy-Wasm plugin example: SSE Timing + +Proxy-Wasm plugin that traces Server-Side Event(SSE) duration from request start. + +### Building + +```sh +$ make +``` + +### Using in Envoy + +This example can be run with [`docker compose`](https://docs.docker.com/compose/install/) +and has a matching Envoy configuration. + +```sh +$ docker compose up +``` + +#### Access granted. + +Send HTTP request to `localhost:10000/`: + +```sh +$ curl localhost:10000/ +``` diff --git a/plugins/wasm-rust/example/sse-timing/docker-compose.yaml b/plugins/wasm-rust/example/sse-timing/docker-compose.yaml new file mode 100644 index 0000000000..78549a2ac7 --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/docker-compose.yaml @@ -0,0 +1,35 @@ +# 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. + +services: + envoy: + image: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/all-in-one:latest + entrypoint: /usr/local/bin/envoy + command: -c /etc/envoy/envoy.yaml --component-log-level wasm:debug + depends_on: + - sse-server + hostname: envoy + ports: + - "10000:10000" + volumes: + - ./envoy.yaml:/etc/envoy/envoy.yaml + - ./target/wasm32-wasi/release:/etc/envoy/proxy-wasm-plugins + networks: + - envoymesh + sse-server: + build: sse-server + networks: + - envoymesh +networks: + envoymesh: {} \ No newline at end of file diff --git a/plugins/wasm-rust/example/sse-timing/envoy.yaml b/plugins/wasm-rust/example/sse-timing/envoy.yaml new file mode 100644 index 0000000000..6281aad0d9 --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/envoy.yaml @@ -0,0 +1,76 @@ +# 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. + +static_resources: + listeners: + - name: listener_0 + address: + socket_address: + protocol: TCP + address: 0.0.0.0 + port_value: 10000 + filter_chains: + - filters: + - name: envoy.filters.network.http_connection_manager + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager + stat_prefix: ingress_http + route_config: + name: local_route + virtual_hosts: + - name: local_service + domains: ["*"] + routes: + - match: + prefix: "/" + route: + cluster: sse-server + http_filters: + - name: envoy.filters.http.wasm + typed_config: + "@type": type.googleapis.com/udpa.type.v1.TypedStruct + type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm + value: + config: + name: "http_body" + configuration: + "@type": type.googleapis.com/google.protobuf.StringValue + value: |- + { + "name": "sse_timing", + "_rules_": [] + } + vm_config: + runtime: "envoy.wasm.runtime.v8" + code: + local: + filename: "/etc/envoy/proxy-wasm-plugins/sse_timing.wasm" + - name: envoy.filters.http.router + typed_config: + "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router + clusters: + - name: sse-server + connect_timeout: 30s + type: LOGICAL_DNS +# dns_lookup_family: V4_ONLY + lb_policy: ROUND_ROBIN + load_assignment: + cluster_name: sse-server + endpoints: + - lb_endpoints: + - endpoint: + address: + socket_address: + address: sse-server + port_value: 8080 \ No newline at end of file diff --git a/plugins/wasm-rust/example/sse-timing/src/lib.rs b/plugins/wasm-rust/example/sse-timing/src/lib.rs new file mode 100644 index 0000000000..ab1fa4f6c4 --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/src/lib.rs @@ -0,0 +1,198 @@ +// 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. + +use higress_wasm_rust::event_stream::EventStream; +use higress_wasm_rust::log::Log; +use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher}; +use proxy_wasm::traits::{Context, HttpContext, RootContext}; +use proxy_wasm::types::{ContextType, DataAction, HeaderAction, LogLevel}; +use serde::Deserialize; +use std::cell::RefCell; +use std::ops::DerefMut; +use std::rc::Rc; +use std::str::from_utf8; +use std::time::{Duration, SystemTime}; + +proxy_wasm::main! {{ + proxy_wasm::set_log_level(LogLevel::Trace); + proxy_wasm::set_root_context(|_|Box::new(SseTimingRoot::new())); +}} + +struct SseTimingRoot { + log: Rc, + rule_matcher: SharedRuleMatcher, +} + +struct SseTiming { + log: Rc, + rule_matcher: SharedRuleMatcher, + vendor: String, + is_event_stream: bool, + event_stream: EventStream, + start_time: SystemTime, +} + +#[derive(Default, Clone, Debug, Deserialize)] +struct SseTimingConfig { + vendor: Option, +} + +impl SseTimingRoot { + fn new() -> Self { + SseTimingRoot { + log: Rc::new(Log::new("sse_timing".to_string())), + rule_matcher: Rc::new(RefCell::new(RuleMatcher::default())), + } + } +} + +impl Context for SseTimingRoot {} + +impl RootContext for SseTimingRoot { + fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool { + on_configure( + self, + _plugin_configuration_size, + self.rule_matcher.borrow_mut().deref_mut(), + &self.log, + ) + } + + fn create_http_context(&self, _context_id: u32) -> Option> { + Some(Box::new(SseTiming { + log: self.log.clone(), + rule_matcher: self.rule_matcher.clone(), + vendor: "higress".into(), + is_event_stream: false, + event_stream: EventStream::new(), + start_time: self.get_current_time(), + })) + } + + fn get_type(&self) -> Option { + Some(ContextType::HttpContext) + } +} + +impl Context for SseTiming {} + +impl HttpContext for SseTiming { + fn on_http_request_headers( + &mut self, + _num_headers: usize, + _end_of_stream: bool, + ) -> HeaderAction { + self.start_time = self.get_current_time(); + + let binding = self.rule_matcher.borrow(); + let config = match binding.get_match_config() { + None => { + return HeaderAction::Continue; + } + Some(config) => config.1, + }; + match config.vendor.clone() { + None => {} + Some(vendor) => self.vendor = vendor, + } + HeaderAction::Continue + } + + fn on_http_response_headers( + &mut self, + _num_headers: usize, + _end_of_stream: bool, + ) -> HeaderAction { + match self.get_http_response_header("Content-Type") { + None => self + .log + .warn("upstream response is not set Content-Type, skipped"), + Some(content_type) => { + if content_type.starts_with("text/event-stream") { + self.is_event_stream = true + } else { + self.log.warn(format!("upstream response Content-Type is not text/event-stream, but {}, skipped", content_type).as_str()) + } + } + } + HeaderAction::Continue + } + + fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction { + if !self.is_event_stream { + return DataAction::Continue; + } + + let body = self + .get_http_response_body(0, body_size) + .unwrap_or_default(); + self.event_stream.update(body); + self.process_event_stream(end_of_stream) + } +} + +impl SseTiming { + fn process_event_stream(&mut self, end_of_stream: bool) -> DataAction { + let mut modified_events = Vec::new(); + + loop { + match self.event_stream.next() { + None => break, + Some(raw_event) => { + if !raw_event.is_empty() { + // according to spec, event-stream must be utf-8 encoding + let event = from_utf8(raw_event.as_slice()).unwrap(); + let processed_event = self.process_event(event.to_string()); + modified_events.push(processed_event); + } + } + } + } + + if end_of_stream { + match self.event_stream.flush() { + None => {} + Some(raw_event) => { + if !raw_event.is_empty() { + // according to spec, event-stream must be utf-8 encoding + let event = from_utf8(raw_event.as_slice()).unwrap(); + let modified_event = self.process_event(event.into()); + modified_events.push(modified_event); + } + } + } + } + + if !modified_events.is_empty() { + let modified_body = modified_events.concat(); + self.set_http_response_body(0, modified_body.len(), modified_body.as_bytes()); + DataAction::Continue + } else { + DataAction::StopIterationNoBuffer + } + } + + fn process_event(&self, event: String) -> String { + let duration = self + .get_current_time() + .duration_since(self.start_time) + .unwrap_or(Duration::ZERO); + format!( + ": server-timing: {};dur={}\n{}\n\n", + self.vendor, + duration.as_millis(), + event + ) + } +} diff --git a/plugins/wasm-rust/example/sse-timing/sse-server/Dockerfile b/plugins/wasm-rust/example/sse-timing/sse-server/Dockerfile new file mode 100644 index 0000000000..7d251e48f9 --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/sse-server/Dockerfile @@ -0,0 +1,5 @@ +FROM golang:latest AS builder +WORKDIR /workspace +COPY . . +RUN GOOS=linux GOARCH=amd64 go build -o main . +CMD ./main \ No newline at end of file diff --git a/plugins/wasm-rust/example/sse-timing/sse-server/go.mod b/plugins/wasm-rust/example/sse-timing/sse-server/go.mod new file mode 100644 index 0000000000..63e8515e6c --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/sse-server/go.mod @@ -0,0 +1,3 @@ +module sse + +go 1.22 diff --git a/plugins/wasm-rust/example/sse-timing/sse-server/main.go b/plugins/wasm-rust/example/sse-timing/sse-server/main.go new file mode 100644 index 0000000000..bb4a3089fd --- /dev/null +++ b/plugins/wasm-rust/example/sse-timing/sse-server/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "log" + "net/http" + "time" +) + +var events = []string{ + ": this is a test stream\n\n", + + "data: some text\n", + "data: another message\n", + "data: with two lines\n\n", + + "event: userconnect\n", + "data: {\"username\": \"bobby\", \"time\": \"02:33:48\"}\n\n", + + "event: usermessage\n", + "data: {\"username\": \"bobby\", \"time\": \"02:34:11\", \"text\": \"Hi everyone.\"}\n\n", + + "event: userdisconnect\n", + "data: {\"username\": \"bobby\", \"time\": \"02:34:23\"}\n\n", + + "event: usermessage\n", + "data: {\"username\": \"sean\", \"time\": \"02:34:36\", \"text\": \"Bye, bobby.\"}\n\n", +} + +func main() { + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + log.Println("receive request") + w.Header().Set("Content-Type", "text/event-stream") + for _, e := range events { + _, _ = w.Write([]byte(e)) + time.Sleep(1 * time.Second) + w.(http.Flusher).Flush() + } + }) + if err := http.ListenAndServe("0.0.0.0:8080", nil); err != nil { + panic(err) + } +} diff --git a/plugins/wasm-rust/src/event_stream.rs b/plugins/wasm-rust/src/event_stream.rs new file mode 100644 index 0000000000..f28846317a --- /dev/null +++ b/plugins/wasm-rust/src/event_stream.rs @@ -0,0 +1,197 @@ +// Copyright (c) 2024 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. + +/// Parsing MIME type text/event-stream according to https://html.spec.whatwg.org/multipage/server-sent-events.html#parsing-an-event-stream +/// +/// The event stream format is as described by the stream production of the following ABNF +/// +/// | rule | expression | +/// |--------|---------------------------| +/// |stream |= [ bom ] *event | +/// |event |= *( comment / field ) eol | +/// |comment |= colon *any-char eol | +/// |field |= 1*name-char [ colon [ space ] *any-char ] eol | +/// |eol |= ( cr lf / cr / lf ) | +/// +/// According to spec, we must judge EOL twice before we can identify a complete event. +/// However, in the rules of event and field, there is an ambiguous grammar in the judgment of eol, +/// and it will bring ambiguity (whether the field ends). In order to eliminate this ambiguity, +/// we believe that CRLF as CR+LF belongs to event and field respectively. +pub struct EventStream { + buffer: Vec, + processed_offset: usize, +} + +impl EventStream { + pub fn new() -> Self { + EventStream { + buffer: Vec::new(), + processed_offset: 0, + } + } + + /// Update the event stream by adding new data to the buffer and resetting processed offset if needed. + pub fn update(&mut self, data: Vec) { + if self.processed_offset > 0 { + self.buffer.drain(0..self.processed_offset); + self.processed_offset = 0; + } + + self.buffer.extend(data); + } + + /// Get the next event from the event stream. Return the event data if available, otherwise return None. + /// Next will consume all the data in the current buffer. However, if there is a valid event at the end of the buffer, + /// it will return the event directly even if the data after the next `update` could be considered part of the same event + /// (especially in cases where CRLF hits an ambiguous grammar). + /// When this happens, the next call to next may return an empty event. + /// + /// ``` + /// let mut parser = EventStream::new(); + /// parser.update(...); + /// loop { + /// match parser.next() { + /// None => {} + /// Some(event) => { + /// if !event.is_empty() { + /// ... + /// } + /// } + /// } + /// } + /// ``` + pub fn next(&mut self) -> Option> { + let mut i = self.processed_offset; + + while i < self.buffer.len() { + if let Some(size) = self.is_2eol(i) { + let event = self.buffer[self.processed_offset..i].to_vec(); + self.processed_offset = i + size; + return Some(event); + } + + i += 1; + } + + None + } + + /// Flush the event stream and return any remaining unprocessed event data. Return None if there is none. + pub fn flush(&mut self) -> Option> { + if self.processed_offset < self.buffer.len() { + let remaining_event = self.buffer[self.processed_offset..].to_vec(); + self.processed_offset = self.buffer.len(); + Some(remaining_event) + } else { + None + } + } + + fn is_eol(&self, i: usize) -> Option { + if i + 1 < self.buffer.len() && self.buffer[i] == b'\r' && self.buffer[i + 1] == b'\n' { + Some(2) + } else if self.buffer[i] == b'\r' || self.buffer[i] == b'\n' { + Some(1) + } else { + None + } + } + + fn is_2eol(&self, i: usize) -> Option { + let size1 = match self.is_eol(i) { + None => return None, + Some(size1) => size1, + }; + if i + size1 < self.buffer.len() { + match self.is_eol(i + size1) { + None => { + if size1 == 2 { + Some(2) + } else { + None + } + } + Some(size2) => Some(size1 + size2), + } + } else if size1 == 2 { + Some(2) + } else { + None + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_crlf_events() { + let mut parser = EventStream::new(); + parser.update(b"event1\n\nevent2\n\n".to_vec()); + + assert_eq!(parser.next(), Some(b"event1".to_vec())); + assert_eq!(parser.next(), Some(b"event2".to_vec())); + } + + #[test] + fn test_lf_events() { + let mut parser = EventStream::new(); + parser.update(b"event3\n\r\nevent4\r\n".to_vec()); + + assert_eq!(parser.next(), Some(b"event3".to_vec())); + assert_eq!(parser.next(), Some(b"event4".to_vec())); + } + + #[test] + fn test_partial_event() { + let mut parser = EventStream::new(); + parser.update(b"partial_event1".to_vec()); + + assert_eq!(parser.next(), None); + + parser.update(b"\n\n".to_vec()); + assert_eq!(parser.next(), Some(b"partial_event1".to_vec())); + } + + #[test] + fn test_mixed_eol_events() { + let mut parser = EventStream::new(); + parser.update(b"event5\r\nevent6\r\n\r\nevent7\r\n".to_vec()); + + assert_eq!(parser.next(), Some(b"event5".to_vec())); + assert_eq!(parser.next(), Some(b"event6".to_vec())); + assert_eq!(parser.next(), Some(b"event7".to_vec())); + } + + #[test] + fn test_mixed2_eol_events() { + let mut parser = EventStream::new(); + parser.update(b"event5\r\nevent6\r\n".to_vec()); + assert_eq!(parser.next(), Some(b"event5".to_vec())); + assert_eq!(parser.next(), Some(b"event6".to_vec())); + parser.update(b"\r\nevent7\r\n".to_vec()); + assert_eq!(parser.next(), Some(b"".to_vec())); + assert_eq!(parser.next(), Some(b"event7".to_vec())); + } + + #[test] + fn test_no_event() { + let mut parser = EventStream::new(); + parser.update(b"no_eol_in_this_string".to_vec()); + + assert_eq!(parser.next(), None); + assert_eq!(parser.flush(), Some(b"no_eol_in_this_string".to_vec())); + } +} diff --git a/plugins/wasm-rust/src/lib.rs b/plugins/wasm-rust/src/lib.rs index 3296ff648a..f6a92be89b 100644 --- a/plugins/wasm-rust/src/lib.rs +++ b/plugins/wasm-rust/src/lib.rs @@ -14,6 +14,7 @@ pub mod cluster_wrapper; pub mod error; +pub mod event_stream; mod internal; pub mod log; pub mod plugin_wrapper; From d952fa562bee3c8e5ce434ce32cf141c6991fb6a Mon Sep 17 00:00:00 2001 From: rinfx <893383980@qq.com> Date: Thu, 24 Oct 2024 17:34:26 +0800 Subject: [PATCH 29/64] bugfix: plugin will block GET request (#1428) --- .../wasm-go/extensions/ai-security-guard/main.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/plugins/wasm-go/extensions/ai-security-guard/main.go b/plugins/wasm-go/extensions/ai-security-guard/main.go index ca59f7f6a1..5b61589616 100644 --- a/plugins/wasm-go/extensions/ai-security-guard/main.go +++ b/plugins/wasm-go/extensions/ai-security-guard/main.go @@ -187,10 +187,6 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config AISecurityConfig, log log.Debugf("request checking is disabled") ctx.DontReadRequestBody() } - if !config.checkResponse { - log.Debugf("response checking is disabled") - ctx.DontReadResponseBody() - } return types.ActionContinue } @@ -199,7 +195,7 @@ func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body [] content := gjson.GetBytes(body, config.requestContentJsonPath).Raw model := gjson.GetBytes(body, "model").Raw ctx.SetContext("requestModel", model) - log.Debugf("Raw response content is: %s", content) + log.Debugf("Raw request content is: %s", content) if len(content) > 0 { timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z") randomID, _ := generateHexID(16) @@ -321,6 +317,11 @@ func reconvertHeaders(hs map[string][]string) [][2]string { } func onHttpResponseHeaders(ctx wrapper.HttpContext, config AISecurityConfig, log wrapper.Log) types.Action { + if !config.checkResponse { + log.Debugf("response checking is disabled") + ctx.DontReadResponseBody() + return types.ActionContinue + } headers, err := proxywasm.GetHttpResponseHeaders() if err != nil { log.Warnf("failed to get response headers: %v", err) @@ -399,7 +400,7 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AISecurityConfig, body [ var jsonData []byte if config.protocolOriginal { jsonData = []byte(denyMessage) - } else if strings.Contains(strings.Join(hdsMap["content-type"], ";"), "event-stream") { + } else if isStreamingResponse { randomID := generateRandomID() jsonData = []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, denyMessage, randomID, model)) } else { From 496d365a955ff09816e9c8d2f04442fe3f07e47f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E8=B4=A4=E6=B6=9B?= <601803023@qq.com> Date: Thu, 24 Oct 2024 20:57:46 +0800 Subject: [PATCH 30/64] add PILOT_ENABLE_ALPHA_GATEWAY_API and fix gateway status update (#1421) --- helm/core/templates/controller-deployment.yaml | 6 ++++++ pkg/config/envs.go | 1 + pkg/ingress/kube/gateway/istio/conversion.go | 3 ++- pkg/ingress/kube/gateway/istio/conversion_test.go | 4 ++-- 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/helm/core/templates/controller-deployment.yaml b/helm/core/templates/controller-deployment.yaml index 98bfe8f6d8..c3bc70aea9 100644 --- a/helm/core/templates/controller-deployment.yaml +++ b/helm/core/templates/controller-deployment.yaml @@ -69,6 +69,10 @@ spec: fieldPath: spec.serviceAccountName - name: DOMAIN_SUFFIX value: {{ .Values.global.proxy.clusterDomain }} + - name: PILOT_ENABLE_ALPHA_GATEWAY_API + value: "true" + - name: GATEWAY_NAME + value: {{ include "gateway.name" . }} {{- if .Values.controller.env }} {{- range $key, $val := .Values.controller.env }} - name: {{ $key }} @@ -218,6 +222,8 @@ spec: {{- if .Values.global.enableGatewayAPI }} - name: PILOT_ENABLE_GATEWAY_API value: "true" + - name: PILOT_ENABLE_ALPHA_GATEWAY_API + value: "true" - name: PILOT_ENABLE_GATEWAY_API_STATUS value: "true" - name: PILOT_ENABLE_GATEWAY_API_DEPLOYMENT_CONTROLLER diff --git a/pkg/config/envs.go b/pkg/config/envs.go index 38ff64bf26..99a67edc65 100644 --- a/pkg/config/envs.go +++ b/pkg/config/envs.go @@ -19,6 +19,7 @@ import "istio.io/pkg/env" var ( PodNamespace = env.RegisterStringVar("POD_NAMESPACE", "higress-system", "").Get() PodName = env.RegisterStringVar("POD_NAME", "", "").Get() + GatewayName = env.RegisterStringVar("GATEWAY_NAME", "higress-gateway", "").Get() // Revision is the value of the Istio control plane revision, e.g. "canary", // and is the value used by the "istio.io/rev" label. Revision = env.Register("REVISION", "", "").Get() diff --git a/pkg/ingress/kube/gateway/istio/conversion.go b/pkg/ingress/kube/gateway/istio/conversion.go index 83c817a717..4cf5dee9e5 100644 --- a/pkg/ingress/kube/gateway/istio/conversion.go +++ b/pkg/ingress/kube/gateway/istio/conversion.go @@ -25,6 +25,7 @@ import ( "strings" higressconfig "github.com/alibaba/higress/pkg/config" + "github.com/alibaba/higress/pkg/ingress/kube/util" istio "istio.io/api/networking/v1alpha3" "istio.io/istio/pilot/pkg/features" "istio.io/istio/pilot/pkg/model" @@ -1880,7 +1881,7 @@ func extractGatewayServices(r GatewayResources, kgw *k8s.GatewaySpec, obj config if len(name) > 0 { return []string{fmt.Sprintf("%s.%s.svc.%v", name, obj.Namespace, r.Domain)}, false, nil } - return []string{}, true, nil + return []string{fmt.Sprintf("%s.%s.svc.%s", higressconfig.GatewayName, higressconfig.PodNamespace, util.GetDomainSuffix())}, true, nil } gatewayServices := []string{} skippedAddresses := []string{} diff --git a/pkg/ingress/kube/gateway/istio/conversion_test.go b/pkg/ingress/kube/gateway/istio/conversion_test.go index 986aecbc69..28f8d26962 100644 --- a/pkg/ingress/kube/gateway/istio/conversion_test.go +++ b/pkg/ingress/kube/gateway/istio/conversion_test.go @@ -919,7 +919,7 @@ func TestExtractGatewayServices(t *testing.T) { Namespace: "default", }, }, - gatewayServices: []string{}, + gatewayServices: []string{"higress-gateway.higress-system.svc.cluster.local"}, useDefaultService: true, }, { @@ -1039,7 +1039,7 @@ func TestExtractGatewayServices(t *testing.T) { Namespace: "default", }, }, - gatewayServices: []string{}, + gatewayServices: []string{"higress-gateway.higress-system.svc.cluster.local"}, useDefaultService: true, }, } From d309bf2e2533d406e28d12b5214623765a07825f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Fri, 25 Oct 2024 16:48:37 +0800 Subject: [PATCH 31/64] fix model-router plugin (#1432) --- .../extensions/model_router/plugin.cc | 2 +- .../extensions/model_router/plugin_test.cc | 56 +++++++++++++++++-- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/plugins/wasm-cpp/extensions/model_router/plugin.cc b/plugins/wasm-cpp/extensions/model_router/plugin.cc index 457864d268..66a90973ff 100644 --- a/plugins/wasm-cpp/extensions/model_router/plugin.cc +++ b/plugins/wasm-cpp/extensions/model_router/plugin.cc @@ -101,7 +101,7 @@ bool PluginRootContext::configure(size_t configuration_size) { configuration_data->view())); return false; } - if (!parseAuthRuleConfig(result.value())) { + if (!parseRuleConfig(result.value())) { LOG_WARN(absl::StrCat("cannot parse plugin configuration JSON string: ", configuration_data->view())); return false; diff --git a/plugins/wasm-cpp/extensions/model_router/plugin_test.cc b/plugins/wasm-cpp/extensions/model_router/plugin_test.cc index 9ce5998051..dc351ecdc8 100644 --- a/plugins/wasm-cpp/extensions/model_router/plugin_test.cc +++ b/plugins/wasm-cpp/extensions/model_router/plugin_test.cc @@ -40,6 +40,11 @@ class MockContext : public proxy_wasm::ContextBase { MOCK_METHOD(WasmResult, getHeaderMapValue, (WasmHeaderMapType /* type */, std::string_view /* key */, std::string_view* /*result */)); + MOCK_METHOD(WasmResult, replaceHeaderMapValue, + (WasmHeaderMapType /* type */, std::string_view /* key */, + std::string_view /* value */)); + MOCK_METHOD(WasmResult, removeHeaderMapValue, + (WasmHeaderMapType /* type */, std::string_view /* key */)); MOCK_METHOD(WasmResult, addHeaderMapValue, (WasmHeaderMapType, std::string_view, std::string_view)); MOCK_METHOD(WasmResult, getProperty, (std::string_view, std::string*)); @@ -87,6 +92,16 @@ class ModelRouterTest : public ::testing::Test { } return WasmResult::Ok; }); + ON_CALL(*mock_context_, + replaceHeaderMapValue(WasmHeaderMapType::RequestHeaders, testing::_, + testing::_)) + .WillByDefault([&](WasmHeaderMapType, std::string_view key, + std::string_view value) { return WasmResult::Ok; }); + ON_CALL(*mock_context_, + removeHeaderMapValue(WasmHeaderMapType::RequestHeaders, testing::_)) + .WillByDefault([&](WasmHeaderMapType, std::string_view key) { + return WasmResult::Ok; + }); ON_CALL(*mock_context_, addHeaderMapValue(WasmHeaderMapType::RequestHeaders, testing::_, testing::_)) .WillByDefault([&](WasmHeaderMapType, std::string_view header, @@ -128,12 +143,45 @@ TEST_F(ModelRouterTest, RewriteModelAndHeader) { return WasmResult::Ok; }); - EXPECT_CALL( - *mock_context_, - addHeaderMapValue(testing::_, std::string_view("x-higress-llm-provider"), - std::string_view("qwen"))); + EXPECT_CALL(*mock_context_, + replaceHeaderMapValue(testing::_, + std::string_view("x-higress-llm-provider"), + std::string_view("qwen"))); + + body_.set(request_json); + EXPECT_EQ(context_->onRequestHeaders(0, false), + FilterHeadersStatus::StopIteration); + EXPECT_EQ(context_->onRequestBody(28, true), FilterDataStatus::Continue); +} + +TEST_F(ModelRouterTest, RouteLevelRewriteModelAndHeader) { + std::string configuration = R"( +{ + "_rules_": [ + { + "_match_route_": ["route-a"], + "enable": true + } +]})"; + + config_.set(configuration); + EXPECT_TRUE(root_context_->configure(configuration.size())); + + std::string request_json = R"({"model": "qwen/qwen-long"})"; + EXPECT_CALL(*mock_context_, + setBuffer(testing::_, testing::_, testing::_, testing::_)) + .WillOnce([&](WasmBufferType, size_t, size_t, std::string_view body) { + EXPECT_EQ(body, R"({"model":"qwen-long"})"); + return WasmResult::Ok; + }); + + EXPECT_CALL(*mock_context_, + replaceHeaderMapValue(testing::_, + std::string_view("x-higress-llm-provider"), + std::string_view("qwen"))); body_.set(request_json); + route_name_ = "route-a"; EXPECT_EQ(context_->onRequestHeaders(0, false), FilterHeadersStatus::StopIteration); EXPECT_EQ(context_->onRequestBody(28, true), FilterDataStatus::Continue); From acec48ed8bf3736a4c05d5c479ea5c27fbb8dc03 Mon Sep 17 00:00:00 2001 From: Yang Beining Date: Sun, 27 Oct 2024 08:21:04 +0000 Subject: [PATCH 32/64] [ai-cache] Implement a WASM plugin for LLM result retrieval based on vector similarity (#1290) --- .../wasm-go/extensions/ai-cache/.gitignore | 2 +- plugins/wasm-go/extensions/ai-cache/README.md | 124 +++++- .../extensions/ai-cache/cache/provider.go | 135 ++++++ .../extensions/ai-cache/cache/redis.go | 58 +++ .../extensions/ai-cache/config/config.go | 225 ++++++++++ plugins/wasm-go/extensions/ai-cache/core.go | 275 ++++++++++++ .../ai-cache/embedding/dashscope.go | 187 ++++++++ .../extensions/ai-cache/embedding/provider.go | 101 +++++ .../extensions/ai-cache/embedding/weaviate.go | 27 ++ plugins/wasm-go/extensions/ai-cache/go.mod | 9 +- plugins/wasm-go/extensions/ai-cache/go.sum | 17 +- plugins/wasm-go/extensions/ai-cache/main.go | 398 +++++------------- plugins/wasm-go/extensions/ai-cache/util.go | 155 +++++++ .../extensions/ai-cache/vector/dashvector.go | 256 +++++++++++ .../extensions/ai-cache/vector/provider.go | 167 ++++++++ plugins/wasm-go/extensions/ai-proxy/Makefile | 4 + plugins/wasm-go/extensions/ai-proxy/go.mod | 2 +- plugins/wasm-go/extensions/ai-proxy/go.sum | 4 +- plugins/wasm-go/extensions/ai-proxy/main.go | 3 +- .../extensions/request-block/Dockerfile | 2 + .../wasm-go/extensions/request-block/Makefile | 4 + .../wasm-go/extensions/request-block/main.go | 2 + plugins/wasm-go/go.mod | 2 +- plugins/wasm-go/go.sum | 8 + plugins/wasm-go/pkg/wrapper/redis_wrapper.go | 5 +- .../e2e/conformance/tests/go-wasm-ai-cache.go | 76 ++++ .../conformance/tests/go-wasm-ai-cache.yaml | 103 +++++ 27 files changed, 2015 insertions(+), 336 deletions(-) create mode 100644 plugins/wasm-go/extensions/ai-cache/cache/provider.go create mode 100644 plugins/wasm-go/extensions/ai-cache/cache/redis.go create mode 100644 plugins/wasm-go/extensions/ai-cache/config/config.go create mode 100644 plugins/wasm-go/extensions/ai-cache/core.go create mode 100644 plugins/wasm-go/extensions/ai-cache/embedding/dashscope.go create mode 100644 plugins/wasm-go/extensions/ai-cache/embedding/provider.go create mode 100644 plugins/wasm-go/extensions/ai-cache/embedding/weaviate.go create mode 100644 plugins/wasm-go/extensions/ai-cache/util.go create mode 100644 plugins/wasm-go/extensions/ai-cache/vector/dashvector.go create mode 100644 plugins/wasm-go/extensions/ai-cache/vector/provider.go create mode 100644 plugins/wasm-go/extensions/ai-proxy/Makefile create mode 100644 plugins/wasm-go/extensions/request-block/Dockerfile create mode 100644 plugins/wasm-go/extensions/request-block/Makefile create mode 100644 test/e2e/conformance/tests/go-wasm-ai-cache.go create mode 100644 test/e2e/conformance/tests/go-wasm-ai-cache.yaml diff --git a/plugins/wasm-go/extensions/ai-cache/.gitignore b/plugins/wasm-go/extensions/ai-cache/.gitignore index 47db8eedba..8a34bf52ad 100644 --- a/plugins/wasm-go/extensions/ai-cache/.gitignore +++ b/plugins/wasm-go/extensions/ai-cache/.gitignore @@ -1,5 +1,5 @@ # File generated by hgctl. Modify as required. - +docker-compose-test/ * !/.gitignore diff --git a/plugins/wasm-go/extensions/ai-cache/README.md b/plugins/wasm-go/extensions/ai-cache/README.md index 1de252f12c..ca91bdf5a1 100644 --- a/plugins/wasm-go/extensions/ai-cache/README.md +++ b/plugins/wasm-go/extensions/ai-cache/README.md @@ -1,9 +1,15 @@ +## 简介 --- title: AI 缓存 keywords: [higress,ai cache] description: AI 缓存插件配置参考 --- +**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" ./` +> ## 功能说明 @@ -19,33 +25,113 @@ LLM 结果缓存插件,默认配置方式可以直接用于 openai 协议的 插件执行阶段:`认证阶段` 插件执行优先级:`10` +## 配置说明 +配置分为 3 个部分:向量数据库(vector);文本向量化接口(embedding);缓存数据库(cache),同时也提供了细粒度的 LLM 请求/响应提取参数配置等。 + ## 配置说明 -| Name | Type | Requirement | Default | Description | -| -------- | -------- | -------- | -------- | -------- | -| cacheKeyFrom.requestBody | string | optional | "messages.@reverse.0.content" | 从请求 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 | -| cacheValueFrom.responseBody | string | optional | "choices.0.message.content" | 从响应 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 | -| cacheStreamValueFrom.responseBody | string | optional | "choices.0.delta.content" | 从流式响应 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 | -| cacheKeyPrefix | string | optional | "higress-ai-cache:" | Redis缓存Key的前缀 | -| cacheTTL | integer | optional | 0 | 缓存的过期时间,单位是秒,默认值为0,即永不过期 | -| redis.serviceName | string | requried | - | redis 服务名称,带服务类型的完整 FQDN 名称,例如 my-redis.dns、redis.my-ns.svc.cluster.local | -| redis.servicePort | integer | optional | 6379 | redis 服务端口 | -| redis.timeout | integer | optional | 1000 | 请求 redis 的超时时间,单位为毫秒 | -| redis.username | string | optional | - | 登陆 redis 的用户名 | -| redis.password | string | optional | - | 登陆 redis 的密码 | -| returnResponseTemplate | string | optional | `{"id":"from-cache","choices":[%s],"model":"gpt-4o","object":"chat.completion","usage":{"prompt_tokens":0,"completion_tokens":0,"total_tokens":0}}` | 返回 HTTP 响应的模版,用 %s 标记需要被 cache value 替换的部分 | -| returnStreamResponseTemplate | string | optional | `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` | 返回流式 HTTP 响应的模版,用 %s 标记需要被 cache value 替换的部分 | +本插件同时支持基于向量数据库的语义化缓存和基于字符串匹配的缓存方法,如果同时配置了向量数据库和缓存数据库,优先使用向量数据库。 + +*Note*: 向量数据库(vector) 和 缓存数据库(cache) 不能同时为空,否则本插件无法提供缓存服务。 + +| Name | Type | Requirement | Default | Description | +| --- | --- | --- | --- | --- | +| vector | string | optional | "" | 向量存储服务提供者类型,例如 dashvector | +| embedding | string | optional | "" | 请求文本向量化服务类型,例如 dashscope | +| cache | string | optional | "" | 缓存服务类型,例如 redis | +| cacheKeyStrategy | string | optional | "lastQuestion" | 决定如何根据历史问题生成缓存键的策略。可选值: "lastQuestion" (使用最后一个问题), "allQuestions" (拼接所有问题) 或 "disabled" (禁用缓存) | +| enableSemanticCache | bool | optional | true | 是否启用语义化缓存, 若不启用,则使用字符串匹配的方式来查找缓存,此时需要配置cache服务 | + +根据是否需要启用语义缓存,可以只配置组件的组合为: +1. `cache`: 仅启用字符串匹配缓存 +3. `vector (+ embedding)`: 启用语义化缓存, 其中若 `vector` 未提供字符串表征服务,则需要自行配置 `embedding` 服务 +2. `vector (+ embedding) + cache`: 启用语义化缓存并用缓存服务存储LLM响应以加速 + +注意若不配置相关组件,则可以忽略相应组件的`required`字段。 + + +## 向量数据库服务(vector) +| Name | Type | Requirement | Default | Description | +| --- | --- | --- | --- | --- | +| vector.type | string | required | "" | 向量存储服务提供者类型,例如 dashvector | +| vector.serviceName | string | required | "" | 向量存储服务名称 | +| vector.serviceHost | string | required | "" | 向量存储服务域名 | +| vector.servicePort | int64 | optional | 443 | 向量存储服务端口 | +| vector.apiKey | string | optional | "" | 向量存储服务 API Key | +| vector.topK | int | optional | 1 | 返回TopK结果,默认为 1 | +| vector.timeout | uint32 | optional | 10000 | 请求向量存储服务的超时时间,单位为毫秒。默认值是10000,即10秒 | +| vector.collectionID | string | optional | "" | dashvector 向量存储服务 Collection ID | +| vector.threshold | float64 | optional | 1000 | 向量相似度度量阈值 | +| vector.thresholdRelation | string | optional | lt | 相似度度量方式有 `Cosine`, `DotProduct`, `Euclidean` 等,前两者值越大相似度越高,后者值越小相似度越高。对于 `Cosine` 和 `DotProduct` 选择 `gt`,对于 `Euclidean` 则选择 `lt`。默认为 `lt`,所有条件包括 `lt` (less than,小于)、`lte` (less than or equal to,小等于)、`gt` (greater than,大于)、`gte` (greater than or equal to,大等于) | + +## 文本向量化服务(embedding) +| Name | Type | Requirement | Default | Description | +| --- | --- | --- | --- | --- | +| embedding.type | string | required | "" | 请求文本向量化服务类型,例如 dashscope | +| embedding.serviceName | string | required | "" | 请求文本向量化服务名称 | +| embedding.serviceHost | string | optional | "" | 请求文本向量化服务域名 | +| embedding.servicePort | int64 | optional | 443 | 请求文本向量化服务端口 | +| embedding.apiKey | string | optional | "" | 请求文本向量化服务的 API Key | +| embedding.timeout | uint32 | optional | 10000 | 请求文本向量化服务的超时时间,单位为毫秒。默认值是10000,即10秒 | +| embedding.model | string | optional | "" | 请求文本向量化服务的模型名称 | + + +## 缓存服务(cache) +| cache.type | string | required | "" | 缓存服务类型,例如 redis | +| --- | --- | --- | --- | --- | +| cache.serviceName | string | required | "" | 缓存服务名称 | +| cache.serviceHost | string | required | "" | 缓存服务域名 | +| cache.servicePort | int64 | optional | 6379 | 缓存服务端口 | +| cache.username | string | optional | "" | 缓存服务用户名 | +| cache.password | string | optional | "" | 缓存服务密码 | +| cache.timeout | uint32 | optional | 10000 | 缓存服务的超时时间,单位为毫秒。默认值是10000,即10秒 | +| cache.cacheTTL | int | optional | 0 | 缓存过期时间,单位为秒。默认值是 0,即 永不过期| +| cacheKeyPrefix | string | optional | "higress-ai-cache:" | 缓存 Key 的前缀,默认值为 "higress-ai-cache:" | + + +## 其他配置 +| Name | Type | Requirement | Default | Description | +| --- | --- | --- | --- | --- | +| cacheKeyFrom | string | optional | "messages.@reverse.0.content" | 从请求 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 | +| cacheValueFrom | string | optional | "choices.0.message.content" | 从响应 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 | +| cacheStreamValueFrom | string | optional | "choices.0.delta.content" | 从流式响应 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 | +| cacheToolCallsFrom | string | optional | "choices.0.delta.content.tool_calls" | 从请求 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 | +| responseTemplate | string | optional | `{"id":"ai-cache.hit","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}}` | 返回 HTTP 响应的模版,用 %s 标记需要被 cache value 替换的部分 | +| streamResponseTemplate | string | optional | `data:{"id":"ai-cache.hit","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` | 返回流式 HTTP 响应的模版,用 %s 标记需要被 cache value 替换的部分 | + ## 配置示例 +### 基础配置 +```yaml +embedding: + type: dashscope + serviceName: my_dashscope.dns + apiKey: [Your Key] + +vector: + type: dashvector + serviceName: my_dashvector.dns + collectionID: [Your Collection ID] + serviceDomain: [Your domain] + apiKey: [Your key] + +cache: + type: redis + serviceName: my_redis.dns + servicePort: 6379 + timeout: 100 +``` + +旧版本配置兼容 ```yaml redis: - serviceName: my-redis.dns - timeout: 2000 + serviceName: my_redis.dns + servicePort: 6379 + timeout: 100 ``` ## 进阶用法 - 当前默认的缓存 key 是基于 GJSON PATH 的表达式:`messages.@reverse.0.content` 提取,含义是把 messages 数组反转后取第一项的 content; GJSON PATH 支持条件判断语法,例如希望取最后一个 role 为 user 的 content 作为 key,可以写成: `messages.@reverse.#(role=="user").content`; @@ -55,3 +141,7 @@ GJSON PATH 支持条件判断语法,例如希望取最后一个 role 为 user 还可以支持管道语法,例如希望取到数第二个 role 为 user 的 content 作为 key,可以写成:`messages.@reverse.#(role=="user")#.content|1`。 更多用法可以参考[官方文档](https://github.com/tidwall/gjson/blob/master/SYNTAX.md),可以使用 [GJSON Playground](https://gjson.dev/) 进行语法测试。 + +## 常见问题 + +1. 如果返回的错误为 `error status returned by host: bad argument`,请检查`serviceName`是否正确包含了服务的类型后缀(.dns等)。 \ No newline at end of file diff --git a/plugins/wasm-go/extensions/ai-cache/cache/provider.go b/plugins/wasm-go/extensions/ai-cache/cache/provider.go new file mode 100644 index 0000000000..1238d21570 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/cache/provider.go @@ -0,0 +1,135 @@ +package cache + +import ( + "errors" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" +) + +const ( + PROVIDER_TYPE_REDIS = "redis" + DEFAULT_CACHE_PREFIX = "higress-ai-cache:" +) + +type providerInitializer interface { + ValidateConfig(ProviderConfig) error + CreateProvider(ProviderConfig) (Provider, error) +} + +var ( + providerInitializers = map[string]providerInitializer{ + PROVIDER_TYPE_REDIS: &redisProviderInitializer{}, + } +) + +type ProviderConfig struct { + // @Title zh-CN redis 缓存服务提供者类型 + // @Description zh-CN 缓存服务提供者类型,例如 redis + typ string + // @Title zh-CN redis 缓存服务名称 + // @Description zh-CN 缓存服务名称 + serviceName string + // @Title zh-CN redis 缓存服务端口 + // @Description zh-CN 缓存服务端口,默认值为6379 + servicePort int + // @Title zh-CN redis 缓存服务地址 + // @Description zh-CN Cache 缓存服务地址,非必填 + serviceHost string + // @Title zh-CN 缓存服务用户名 + // @Description zh-CN 缓存服务用户名,非必填 + username string + // @Title zh-CN 缓存服务密码 + // @Description zh-CN 缓存服务密码,非必填 + password string + // @Title zh-CN 请求超时 + // @Description zh-CN 请求缓存服务的超时时间,单位为毫秒。默认值是10000,即10秒 + timeout uint32 + // @Title zh-CN 缓存过期时间 + // @Description zh-CN 缓存过期时间,单位为秒。默认值是0,即永不过期 + cacheTTL int + // @Title 缓存 Key 前缀 + // @Description 缓存 Key 的前缀,默认值为 "higressAiCache:" + cacheKeyPrefix string +} + +func (c *ProviderConfig) GetProviderType() string { + return c.typ +} + +func (c *ProviderConfig) FromJson(json gjson.Result) { + c.typ = json.Get("type").String() + c.serviceName = json.Get("serviceName").String() + c.servicePort = int(json.Get("servicePort").Int()) + if !json.Get("servicePort").Exists() { + c.servicePort = 6379 + } + c.serviceHost = json.Get("serviceHost").String() + c.username = json.Get("username").String() + if !json.Get("username").Exists() { + c.username = "" + } + c.password = json.Get("password").String() + if !json.Get("password").Exists() { + c.password = "" + } + c.timeout = uint32(json.Get("timeout").Int()) + if !json.Get("timeout").Exists() { + c.timeout = 10000 + } + c.cacheTTL = int(json.Get("cacheTTL").Int()) + if !json.Get("cacheTTL").Exists() { + c.cacheTTL = 0 + // c.cacheTTL = 3600000 + } + if json.Get("cacheKeyPrefix").Exists() { + c.cacheKeyPrefix = json.Get("cacheKeyPrefix").String() + } else { + c.cacheKeyPrefix = DEFAULT_CACHE_PREFIX + } + +} + +func (c *ProviderConfig) ConvertLegacyJson(json gjson.Result) { + c.FromJson(json.Get("redis")) + c.typ = "redis" + if json.Get("cacheTTL").Exists() { + c.cacheTTL = int(json.Get("cacheTTL").Int()) + } +} + +func (c *ProviderConfig) Validate() error { + if c.typ == "" { + return errors.New("cache service type is required") + } + if c.serviceName == "" { + return errors.New("cache service name is required") + } + if c.cacheTTL < 0 { + return errors.New("cache TTL must be greater than or equal to 0") + } + initializer, has := providerInitializers[c.typ] + if !has { + return errors.New("unknown cache service provider type: " + c.typ) + } + if err := initializer.ValidateConfig(*c); err != nil { + return err + } + return nil +} + +func CreateProvider(pc ProviderConfig) (Provider, error) { + initializer, has := providerInitializers[pc.typ] + if !has { + return nil, errors.New("unknown provider type: " + pc.typ) + } + return initializer.CreateProvider(pc) +} + +type Provider interface { + GetProviderType() string + Init(username string, password string, timeout uint32) error + Get(key string, cb wrapper.RedisResponseCallback) error + Set(key string, value string, cb wrapper.RedisResponseCallback) error + GetCacheKeyPrefix() string +} diff --git a/plugins/wasm-go/extensions/ai-cache/cache/redis.go b/plugins/wasm-go/extensions/ai-cache/cache/redis.go new file mode 100644 index 0000000000..4cb69744e1 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/cache/redis.go @@ -0,0 +1,58 @@ +package cache + +import ( + "errors" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +) + +type redisProviderInitializer struct { +} + +func (r *redisProviderInitializer) ValidateConfig(cf ProviderConfig) error { + if len(cf.serviceName) == 0 { + return errors.New("cache service name is required") + } + return nil +} + +func (r *redisProviderInitializer) CreateProvider(cf ProviderConfig) (Provider, error) { + rp := redisProvider{ + config: cf, + client: wrapper.NewRedisClusterClient(wrapper.FQDNCluster{ + FQDN: cf.serviceName, + Host: cf.serviceHost, + Port: int64(cf.servicePort)}), + } + err := rp.Init(cf.username, cf.password, cf.timeout) + return &rp, err +} + +type redisProvider struct { + config ProviderConfig + client wrapper.RedisClient +} + +func (rp *redisProvider) GetProviderType() string { + return PROVIDER_TYPE_REDIS +} + +func (rp *redisProvider) Init(username string, password string, timeout uint32) error { + return rp.client.Init(rp.config.username, rp.config.password, int64(rp.config.timeout)) +} + +func (rp *redisProvider) Get(key string, cb wrapper.RedisResponseCallback) error { + return rp.client.Get(key, cb) +} + +func (rp *redisProvider) Set(key string, value string, cb wrapper.RedisResponseCallback) error { + if rp.config.cacheTTL == 0 { + return rp.client.Set(key, value, cb) + } else { + return rp.client.SetEx(key, value, rp.config.cacheTTL, cb) + } +} + +func (rp *redisProvider) GetCacheKeyPrefix() string { + return rp.config.cacheKeyPrefix +} diff --git a/plugins/wasm-go/extensions/ai-cache/config/config.go b/plugins/wasm-go/extensions/ai-cache/config/config.go new file mode 100644 index 0000000000..4bd6e2a18f --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/config/config.go @@ -0,0 +1,225 @@ +package config + +import ( + "fmt" + + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/cache" + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/embedding" + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/vector" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" +) + +const ( + CACHE_KEY_STRATEGY_LAST_QUESTION = "lastQuestion" + CACHE_KEY_STRATEGY_ALL_QUESTIONS = "allQuestions" + CACHE_KEY_STRATEGY_DISABLED = "disabled" +) + +type PluginConfig struct { + // @Title zh-CN 返回 HTTP 响应的模版 + // @Description zh-CN 用 %s 标记需要被 cache value 替换的部分 + ResponseTemplate string + // @Title zh-CN 返回流式 HTTP 响应的模版 + // @Description zh-CN 用 %s 标记需要被 cache value 替换的部分 + StreamResponseTemplate string + + cacheProvider cache.Provider + embeddingProvider embedding.Provider + vectorProvider vector.Provider + + embeddingProviderConfig embedding.ProviderConfig + vectorProviderConfig vector.ProviderConfig + cacheProviderConfig cache.ProviderConfig + + CacheKeyFrom string + CacheValueFrom string + CacheStreamValueFrom string + CacheToolCallsFrom string + + // @Title zh-CN 启用语义化缓存 + // @Description zh-CN 控制是否启用语义化缓存功能。true 表示启用,false 表示禁用。 + EnableSemanticCache bool + + // @Title zh-CN 缓存键策略 + // @Description zh-CN 决定如何生成缓存键的策略。可选值: "lastQuestion" (使用最后一个问题), "allQuestions" (拼接所有问题) 或 "disabled" (禁用缓存) + CacheKeyStrategy string +} + +func (c *PluginConfig) FromJson(json gjson.Result, log wrapper.Log) { + + c.vectorProviderConfig.FromJson(json.Get("vector")) + c.embeddingProviderConfig.FromJson(json.Get("embedding")) + c.cacheProviderConfig.FromJson(json.Get("cache")) + if json.Get("redis").Exists() { + // compatible with legacy config + c.cacheProviderConfig.ConvertLegacyJson(json) + } + + c.CacheKeyStrategy = json.Get("cacheKeyStrategy").String() + if c.CacheKeyStrategy == "" { + c.CacheKeyStrategy = CACHE_KEY_STRATEGY_LAST_QUESTION // set default value + } + c.CacheKeyFrom = json.Get("cacheKeyFrom").String() + if c.CacheKeyFrom == "" { + c.CacheKeyFrom = "messages.@reverse.0.content" + } + c.CacheValueFrom = json.Get("cacheValueFrom").String() + if c.CacheValueFrom == "" { + c.CacheValueFrom = "choices.0.message.content" + } + c.CacheStreamValueFrom = json.Get("cacheStreamValueFrom").String() + if c.CacheStreamValueFrom == "" { + c.CacheStreamValueFrom = "choices.0.delta.content" + } + c.CacheToolCallsFrom = json.Get("cacheToolCallsFrom").String() + if c.CacheToolCallsFrom == "" { + c.CacheToolCallsFrom = "choices.0.delta.content.tool_calls" + } + + 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.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}}` + } + + if json.Get("enableSemanticCache").Exists() { + c.EnableSemanticCache = json.Get("enableSemanticCache").Bool() + } else { + c.EnableSemanticCache = true // set default value to true + } + + // compatible with legacy config + convertLegacyMapFields(c, json, log) +} + +func (c *PluginConfig) Validate() error { + // if cache provider is configured, validate it + if c.cacheProviderConfig.GetProviderType() != "" { + if err := c.cacheProviderConfig.Validate(); err != nil { + return err + } + } + if c.embeddingProviderConfig.GetProviderType() != "" { + if err := c.embeddingProviderConfig.Validate(); err != nil { + return err + } + } + if c.vectorProviderConfig.GetProviderType() != "" { + if err := c.vectorProviderConfig.Validate(); err != nil { + return err + } + } + + // cache, vector, and embedding cannot all be empty + if c.vectorProviderConfig.GetProviderType() == "" && + c.embeddingProviderConfig.GetProviderType() == "" && + c.cacheProviderConfig.GetProviderType() == "" { + return fmt.Errorf("vector, embedding and cache provider cannot be all empty") + } + + // Validate the value of CacheKeyStrategy + if c.CacheKeyStrategy != CACHE_KEY_STRATEGY_LAST_QUESTION && + c.CacheKeyStrategy != CACHE_KEY_STRATEGY_ALL_QUESTIONS && + c.CacheKeyStrategy != CACHE_KEY_STRATEGY_DISABLED { + return fmt.Errorf("invalid CacheKeyStrategy: %s", c.CacheKeyStrategy) + } + + // If semantic cache is enabled, ensure necessary components are configured + // if c.EnableSemanticCache { + // if c.embeddingProviderConfig.GetProviderType() == "" { + // return fmt.Errorf("semantic cache is enabled but embedding provider is not configured") + // } + // // if only configure cache, just warn the user + // } + return nil +} + +func (c *PluginConfig) Complete(log wrapper.Log) error { + var err error + if c.embeddingProviderConfig.GetProviderType() != "" { + log.Debugf("embedding provider is set to %s", c.embeddingProviderConfig.GetProviderType()) + c.embeddingProvider, err = embedding.CreateProvider(c.embeddingProviderConfig) + if err != nil { + return err + } + } else { + log.Info("embedding provider is not configured") + c.embeddingProvider = nil + } + if c.cacheProviderConfig.GetProviderType() != "" { + log.Debugf("cache provider is set to %s", c.cacheProviderConfig.GetProviderType()) + c.cacheProvider, err = cache.CreateProvider(c.cacheProviderConfig) + if err != nil { + return err + } + } else { + log.Info("cache provider is not configured") + c.cacheProvider = nil + } + if c.vectorProviderConfig.GetProviderType() != "" { + log.Debugf("vector provider is set to %s", c.vectorProviderConfig.GetProviderType()) + c.vectorProvider, err = vector.CreateProvider(c.vectorProviderConfig) + if err != nil { + return err + } + } else { + log.Info("vector provider is not configured") + c.vectorProvider = nil + } + return nil +} + +func (c *PluginConfig) GetEmbeddingProvider() embedding.Provider { + return c.embeddingProvider +} + +func (c *PluginConfig) GetVectorProvider() vector.Provider { + return c.vectorProvider +} + +func (c *PluginConfig) GetVectorProviderConfig() vector.ProviderConfig { + return c.vectorProviderConfig +} + +func (c *PluginConfig) GetCacheProvider() cache.Provider { + return c.cacheProvider +} + +func convertLegacyMapFields(c *PluginConfig, json gjson.Result, log wrapper.Log) { + keyMap := map[string]string{ + "cacheKeyFrom.requestBody": "cacheKeyFrom", + "cacheValueFrom.requestBody": "cacheValueFrom", + "cacheStreamValueFrom.requestBody": "cacheStreamValueFrom", + "returnResponseTemplate": "responseTemplate", + "returnStreamResponseTemplate": "streamResponseTemplate", + } + + for oldKey, newKey := range keyMap { + if json.Get(oldKey).Exists() { + log.Debugf("[convertLegacyMapFields] mapping %s to %s", oldKey, newKey) + setField(c, newKey, json.Get(oldKey).String(), log) + } else { + log.Debugf("[convertLegacyMapFields] %s not exists", oldKey) + } + } +} + +func setField(c *PluginConfig, fieldName string, value string, log wrapper.Log) { + switch fieldName { + case "cacheKeyFrom": + c.CacheKeyFrom = value + case "cacheValueFrom": + c.CacheValueFrom = value + case "cacheStreamValueFrom": + c.CacheStreamValueFrom = value + case "responseTemplate": + c.ResponseTemplate = value + case "streamResponseTemplate": + c.StreamResponseTemplate = value + } + log.Debugf("[setField] set %s to %s", fieldName, value) +} diff --git a/plugins/wasm-go/extensions/ai-cache/core.go b/plugins/wasm-go/extensions/ai-cache/core.go new file mode 100644 index 0000000000..19a9b2b856 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/core.go @@ -0,0 +1,275 @@ +package main + +import ( + "errors" + "fmt" + "strconv" + "strings" + + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/config" + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/vector" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" + "github.com/tidwall/resp" +) + +// CheckCacheForKey checks if the key is in the cache, or triggers similarity search if not found. +func CheckCacheForKey(key string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, stream bool, useSimilaritySearch bool) error { + activeCacheProvider := c.GetCacheProvider() + if activeCacheProvider == nil { + log.Debugf("[%s] [CheckCacheForKey] no cache provider configured, performing similarity search", PLUGIN_NAME) + return performSimilaritySearch(key, ctx, c, log, key, stream) + } + + queryKey := activeCacheProvider.GetCacheKeyPrefix() + key + log.Debugf("[%s] [CheckCacheForKey] querying cache with key: %s", PLUGIN_NAME, queryKey) + + err := activeCacheProvider.Get(queryKey, func(response resp.Value) { + handleCacheResponse(key, response, ctx, log, stream, c, useSimilaritySearch) + }) + + if err != nil { + log.Errorf("[%s] [CheckCacheForKey] failed to retrieve key: %s from cache, error: %v", PLUGIN_NAME, key, err) + return err + } + + return nil +} + +// handleCacheResponse processes cache response and handles cache hits and misses. +func handleCacheResponse(key string, response resp.Value, ctx wrapper.HttpContext, log wrapper.Log, stream bool, c config.PluginConfig, useSimilaritySearch bool) { + if err := response.Error(); err == nil && !response.IsNull() { + log.Infof("[%s] cache hit for key: %s", PLUGIN_NAME, key) + processCacheHit(key, response.String(), stream, ctx, c, log) + return + } + + log.Infof("[%s] [handleCacheResponse] cache miss for key: %s", PLUGIN_NAME, key) + if err := response.Error(); err != nil { + log.Errorf("[%s] [handleCacheResponse] error retrieving key: %s from cache, error: %v", PLUGIN_NAME, key, err) + } + + if useSimilaritySearch && c.EnableSemanticCache { + if err := performSimilaritySearch(key, ctx, c, log, key, stream); err != nil { + log.Errorf("[%s] [handleCacheResponse] failed to perform similarity search for key: %s, error: %v", PLUGIN_NAME, key, err) + proxywasm.ResumeHttpRequest() + } + } else { + proxywasm.ResumeHttpRequest() + } +} + +// processCacheHit handles a successful cache hit. +func processCacheHit(key string, response string, stream bool, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log) { + if strings.TrimSpace(response) == "" { + log.Warnf("[%s] [processCacheHit] cached response for key %s is empty", PLUGIN_NAME, key) + proxywasm.ResumeHttpRequest() + return + } + + log.Debugf("[%s] [processCacheHit] cached response for key %s: %s", PLUGIN_NAME, key, response) + + // Escape the response to ensure consistent formatting + escapedResponse := strings.Trim(strconv.Quote(response), "\"") + + ctx.SetContext(CACHE_KEY_CONTEXT_KEY, nil) + + if stream { + proxywasm.SendHttpResponseWithDetail(200, "ai-cache.hit", [][2]string{{"content-type", "text/event-stream; charset=utf-8"}}, []byte(fmt.Sprintf(c.StreamResponseTemplate, escapedResponse)), -1) + } else { + proxywasm.SendHttpResponseWithDetail(200, "ai-cache.hit", [][2]string{{"content-type", "application/json; charset=utf-8"}}, []byte(fmt.Sprintf(c.ResponseTemplate, escapedResponse)), -1) + } +} + +// performSimilaritySearch determines the appropriate similarity search method to use. +func performSimilaritySearch(key string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, queryString string, stream bool) error { + activeVectorProvider := c.GetVectorProvider() + if activeVectorProvider == nil { + return logAndReturnError(log, "[performSimilaritySearch] no vector provider configured for similarity search") + } + + // Check if the active vector provider implements the StringQuerier interface. + if _, ok := activeVectorProvider.(vector.StringQuerier); ok { + log.Debugf("[%s] [performSimilaritySearch] active vector provider implements StringQuerier interface, performing string query", PLUGIN_NAME) + return performStringQuery(key, queryString, ctx, c, log, stream) + } + + // Check if the active vector provider implements the EmbeddingQuerier interface. + if _, ok := activeVectorProvider.(vector.EmbeddingQuerier); ok { + log.Debugf("[%s] [performSimilaritySearch] active vector provider implements EmbeddingQuerier interface, performing embedding query", PLUGIN_NAME) + return performEmbeddingQuery(key, ctx, c, log, stream) + } + + return logAndReturnError(log, "[performSimilaritySearch] no suitable querier or embedding provider available for similarity search") +} + +// performStringQuery executes the string-based similarity search. +func performStringQuery(key string, queryString string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, stream bool) error { + stringQuerier, ok := c.GetVectorProvider().(vector.StringQuerier) + if !ok { + return logAndReturnError(log, "[performStringQuery] active vector provider does not implement StringQuerier interface") + } + + return stringQuerier.QueryString(queryString, ctx, log, func(results []vector.QueryResult, ctx wrapper.HttpContext, log wrapper.Log, err error) { + handleQueryResults(key, results, ctx, log, stream, c, err) + }) +} + +// performEmbeddingQuery executes the embedding-based similarity search. +func performEmbeddingQuery(key string, ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log, stream bool) error { + embeddingQuerier, ok := c.GetVectorProvider().(vector.EmbeddingQuerier) + if !ok { + return logAndReturnError(log, fmt.Sprintf("[performEmbeddingQuery] active vector provider does not implement EmbeddingQuerier interface")) + } + + activeEmbeddingProvider := c.GetEmbeddingProvider() + if activeEmbeddingProvider == nil { + return logAndReturnError(log, fmt.Sprintf("[performEmbeddingQuery] no embedding provider configured for similarity search")) + } + + return activeEmbeddingProvider.GetEmbedding(key, ctx, log, func(textEmbedding []float64, err error) { + log.Debugf("[%s] [performEmbeddingQuery] GetEmbedding success, length of embedding: %d, error: %v", PLUGIN_NAME, len(textEmbedding), err) + if err != nil { + handleInternalError(err, fmt.Sprintf("[%s] [performEmbeddingQuery] error getting embedding for key: %s", PLUGIN_NAME, key), log) + return + } + ctx.SetContext(CACHE_KEY_EMBEDDING_KEY, textEmbedding) + + err = embeddingQuerier.QueryEmbedding(textEmbedding, ctx, log, func(results []vector.QueryResult, ctx wrapper.HttpContext, log wrapper.Log, err error) { + handleQueryResults(key, results, ctx, log, stream, c, err) + }) + if err != nil { + handleInternalError(err, fmt.Sprintf("[%s] [performEmbeddingQuery] error querying vector database for key: %s", PLUGIN_NAME, key), log) + } + }) +} + +// handleQueryResults processes the results of similarity search and determines next actions. +func handleQueryResults(key string, results []vector.QueryResult, ctx wrapper.HttpContext, log wrapper.Log, stream bool, c config.PluginConfig, err error) { + if err != nil { + handleInternalError(err, fmt.Sprintf("[%s] [handleQueryResults] error querying vector database for key: %s", PLUGIN_NAME, key), log) + return + } + + if len(results) == 0 { + log.Warnf("[%s] [handleQueryResults] no similar keys found for key: %s", PLUGIN_NAME, key) + proxywasm.ResumeHttpRequest() + return + } + + mostSimilarData := results[0] + log.Debugf("[%s] [handleQueryResults] for key: %s, the most similar key found: %s with score: %f", PLUGIN_NAME, key, mostSimilarData.Text, mostSimilarData.Score) + simThreshold := c.GetVectorProviderConfig().Threshold + simThresholdRelation := c.GetVectorProviderConfig().ThresholdRelation + if compare(simThresholdRelation, mostSimilarData.Score, simThreshold) { + log.Infof("[%s] key accepted: %s with score: %f", PLUGIN_NAME, mostSimilarData.Text, mostSimilarData.Score) + if mostSimilarData.Answer != "" { + // direct return the answer if available + cacheResponse(ctx, c, key, mostSimilarData.Answer, log) + processCacheHit(key, mostSimilarData.Answer, stream, ctx, c, log) + } else { + if c.GetCacheProvider() != nil { + CheckCacheForKey(mostSimilarData.Text, ctx, c, log, stream, false) + } else { + // Otherwise, do not check the cache, directly return + log.Infof("[%s] cache hit for key: %s, but no corresponding answer found in the vector database", PLUGIN_NAME, mostSimilarData.Text) + proxywasm.ResumeHttpRequest() + } + } + } else { + log.Infof("[%s] score not meet the threshold %f: %s with score %f", PLUGIN_NAME, simThreshold, mostSimilarData.Text, mostSimilarData.Score) + proxywasm.ResumeHttpRequest() + } +} + +// logAndReturnError logs an error and returns it. +func logAndReturnError(log wrapper.Log, message string) error { + message = fmt.Sprintf("[%s] %s", PLUGIN_NAME, message) + log.Errorf(message) + return errors.New(message) +} + +// handleInternalError logs an error and resumes the HTTP request. +func handleInternalError(err error, message string, log wrapper.Log) { + if err != nil { + log.Errorf("[%s] [handleInternalError] %s: %v", PLUGIN_NAME, message, err) + } else { + log.Errorf("[%s] [handleInternalError] %s", PLUGIN_NAME, message) + } + // proxywasm.SendHttpResponse(500, [][2]string{{"content-type", "text/plain"}}, []byte("Internal Server Error"), -1) + proxywasm.ResumeHttpRequest() +} + +// Caches the response value +func cacheResponse(ctx wrapper.HttpContext, c config.PluginConfig, key string, value string, log wrapper.Log) { + if strings.TrimSpace(value) == "" { + log.Warnf("[%s] [cacheResponse] cached value for key %s is empty", PLUGIN_NAME, key) + return + } + + activeCacheProvider := c.GetCacheProvider() + if activeCacheProvider != nil { + queryKey := activeCacheProvider.GetCacheKeyPrefix() + key + _ = activeCacheProvider.Set(queryKey, value, nil) + log.Debugf("[%s] [cacheResponse] cache set success, key: %s, length of value: %d", PLUGIN_NAME, queryKey, len(value)) + } +} + +// Handles embedding upload if available +func uploadEmbeddingAndAnswer(ctx wrapper.HttpContext, c config.PluginConfig, key string, value string, log wrapper.Log) { + embedding := ctx.GetContext(CACHE_KEY_EMBEDDING_KEY) + if embedding == nil { + return + } + + emb, ok := embedding.([]float64) + if !ok { + log.Errorf("[%s] [uploadEmbeddingAndAnswer] embedding is not of expected type []float64", PLUGIN_NAME) + return + } + + activeVectorProvider := c.GetVectorProvider() + if activeVectorProvider == nil { + log.Debugf("[%s] [uploadEmbeddingAndAnswer] no vector provider configured for uploading embedding", PLUGIN_NAME) + return + } + + // Attempt to upload answer embedding first + if ansEmbUploader, ok := activeVectorProvider.(vector.AnswerAndEmbeddingUploader); ok { + log.Infof("[%s] uploading answer embedding for key: %s", PLUGIN_NAME, key) + err := ansEmbUploader.UploadAnswerAndEmbedding(key, emb, value, ctx, log, nil) + if err != nil { + log.Warnf("[%s] [uploadEmbeddingAndAnswer] failed to upload answer embedding for key: %s, error: %v", PLUGIN_NAME, key, err) + } else { + return // If successful, return early + } + } + + // If answer embedding upload fails, attempt normal embedding upload + if embUploader, ok := activeVectorProvider.(vector.EmbeddingUploader); ok { + log.Infof("[%s] uploading embedding for key: %s", PLUGIN_NAME, key) + err := embUploader.UploadEmbedding(key, emb, ctx, log, nil) + if err != nil { + log.Warnf("[%s] [uploadEmbeddingAndAnswer] failed to upload embedding for key: %s, error: %v", PLUGIN_NAME, key, err) + } + } +} + +// 主要用于相似度/距离/点积判断 +// 余弦相似度度量的是两个向量在方向上的相似程度。相似度越高,两个向量越接近。 +// 距离度量的是两个向量在空间上的远近程度。距离越小,两个向量越接近。 +// compare 函数根据操作符进行判断并返回结果 +func compare(operator string, value1 float64, value2 float64) bool { + switch operator { + case "gt": + return value1 > value2 + case "gte": + return value1 >= value2 + case "lt": + return value1 < value2 + case "lte": + return value1 <= value2 + default: + return false + } +} diff --git a/plugins/wasm-go/extensions/ai-cache/embedding/dashscope.go b/plugins/wasm-go/extensions/ai-cache/embedding/dashscope.go new file mode 100644 index 0000000000..35c897cce5 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/embedding/dashscope.go @@ -0,0 +1,187 @@ +package embedding + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +) + +const ( + DASHSCOPE_DOMAIN = "dashscope.aliyuncs.com" + DASHSCOPE_PORT = 443 + DASHSCOPE_DEFAULT_MODEL_NAME = "text-embedding-v2" + DASHSCOPE_ENDPOINT = "/api/v1/services/embeddings/text-embedding/text-embedding" +) + +type dashScopeProviderInitializer struct { +} + +func (d *dashScopeProviderInitializer) ValidateConfig(config ProviderConfig) error { + if config.apiKey == "" { + return errors.New("[DashScope] apiKey is required") + } + return nil +} + +func (d *dashScopeProviderInitializer) CreateProvider(c ProviderConfig) (Provider, error) { + if c.servicePort == 0 { + c.servicePort = DASHSCOPE_PORT + } + if c.serviceHost == "" { + c.serviceHost = DASHSCOPE_DOMAIN + } + return &DSProvider{ + config: c, + client: wrapper.NewClusterClient(wrapper.FQDNCluster{ + FQDN: c.serviceName, + Host: c.serviceHost, + Port: int64(c.servicePort), + }), + }, nil +} + +func (d *DSProvider) GetProviderType() string { + return PROVIDER_TYPE_DASHSCOPE +} + +type Embedding struct { + Embedding []float64 `json:"embedding"` + TextIndex int `json:"text_index"` +} + +type Input struct { + Texts []string `json:"texts"` +} + +type Params struct { + TextType string `json:"text_type"` +} + +type Response struct { + RequestID string `json:"request_id"` + Output Output `json:"output"` + Usage Usage `json:"usage"` +} + +type Output struct { + Embeddings []Embedding `json:"embeddings"` +} + +type Usage struct { + TotalTokens int `json:"total_tokens"` +} + +type EmbeddingRequest struct { + Model string `json:"model"` + Input Input `json:"input"` + Parameters Params `json:"parameters"` +} + +type Document struct { + Vector []float64 `json:"vector"` + Fields map[string]string `json:"fields"` +} + +type DSProvider struct { + config ProviderConfig + client wrapper.HttpClient +} + +func (d *DSProvider) constructParameters(texts []string, log wrapper.Log) (string, [][2]string, []byte, error) { + + model := d.config.model + + if model == "" { + model = DASHSCOPE_DEFAULT_MODEL_NAME + } + data := EmbeddingRequest{ + Model: model, + Input: Input{ + Texts: texts, + }, + Parameters: Params{ + TextType: "query", + }, + } + + requestBody, err := json.Marshal(data) + if err != nil { + log.Errorf("failed to marshal request data: %v", err) + return "", nil, nil, err + } + + if d.config.apiKey == "" { + err := errors.New("dashScopeKey is empty") + log.Errorf("failed to construct headers: %v", err) + return "", nil, nil, err + } + + headers := [][2]string{ + {"Authorization", "Bearer " + d.config.apiKey}, + {"Content-Type", "application/json"}, + } + + return DASHSCOPE_ENDPOINT, headers, requestBody, err +} + +type Result struct { + ID string `json:"id"` + Vector []float64 `json:"vector,omitempty"` + Fields map[string]interface{} `json:"fields"` + Score float64 `json:"score"` +} + +func (d *DSProvider) parseTextEmbedding(responseBody []byte) (*Response, error) { + var resp Response + err := json.Unmarshal(responseBody, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (d *DSProvider) GetEmbedding( + queryString string, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(emb []float64, err error)) error { + embUrl, embHeaders, embRequestBody, err := d.constructParameters([]string{queryString}, log) + if err != nil { + log.Errorf("failed to construct parameters: %v", err) + return err + } + + var resp *Response + err = d.client.Post(embUrl, embHeaders, embRequestBody, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + + if statusCode != http.StatusOK { + err = errors.New("failed to get embedding due to status code: " + strconv.Itoa(statusCode)) + callback(nil, err) + return + } + + log.Debugf("get embedding response: %d, %s", statusCode, responseBody) + + resp, err = d.parseTextEmbedding(responseBody) + if err != nil { + err = fmt.Errorf("failed to parse response: %v", err) + callback(nil, err) + return + } + + if len(resp.Output.Embeddings) == 0 { + err = errors.New("no embedding found in response") + callback(nil, err) + return + } + + callback(resp.Output.Embeddings[0].Embedding, nil) + + }, d.config.timeout) + return err +} diff --git a/plugins/wasm-go/extensions/ai-cache/embedding/provider.go b/plugins/wasm-go/extensions/ai-cache/embedding/provider.go new file mode 100644 index 0000000000..909edf129c --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/embedding/provider.go @@ -0,0 +1,101 @@ +package embedding + +import ( + "errors" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" +) + +const ( + PROVIDER_TYPE_DASHSCOPE = "dashscope" +) + +type providerInitializer interface { + ValidateConfig(ProviderConfig) error + CreateProvider(ProviderConfig) (Provider, error) +} + +var ( + providerInitializers = map[string]providerInitializer{ + PROVIDER_TYPE_DASHSCOPE: &dashScopeProviderInitializer{}, + } +) + +type ProviderConfig struct { + // @Title zh-CN 文本特征提取服务提供者类型 + // @Description zh-CN 文本特征提取服务提供者类型,例如 DashScope + typ string + // @Title zh-CN DashScope 文本特征提取服务名称 + // @Description zh-CN 文本特征提取服务名称 + serviceName string + // @Title zh-CN 文本特征提取服务域名 + // @Description zh-CN 文本特征提取服务域名 + serviceHost string + // @Title zh-CN 文本特征提取服务端口 + // @Description zh-CN 文本特征提取服务端口 + servicePort int64 + // @Title zh-CN 文本特征提取服务 API Key + // @Description zh-CN 文本特征提取服务 API Key + apiKey string + // @Title zh-CN 文本特征提取服务超时时间 + // @Description zh-CN 文本特征提取服务超时时间 + timeout uint32 + // @Title zh-CN 文本特征提取服务使用的模型 + // @Description zh-CN 用于文本特征提取的模型名称, 在 DashScope 中默认为 "text-embedding-v1" + model string +} + +func (c *ProviderConfig) FromJson(json gjson.Result) { + c.typ = json.Get("type").String() + c.serviceName = json.Get("serviceName").String() + c.serviceHost = json.Get("serviceHost").String() + c.servicePort = json.Get("servicePort").Int() + c.apiKey = json.Get("apiKey").String() + c.timeout = uint32(json.Get("timeout").Int()) + c.model = json.Get("model").String() + if c.timeout == 0 { + c.timeout = 10000 + } +} + +func (c *ProviderConfig) Validate() error { + if c.serviceName == "" { + return errors.New("embedding service name is required") + } + if c.apiKey == "" { + return errors.New("embedding service API key is required") + } + if c.typ == "" { + return errors.New("embedding service type is required") + } + initializer, has := providerInitializers[c.typ] + if !has { + return errors.New("unknown embedding service provider type: " + c.typ) + } + if err := initializer.ValidateConfig(*c); err != nil { + return err + } + return nil +} + +func (c *ProviderConfig) GetProviderType() string { + return c.typ +} + +func CreateProvider(pc ProviderConfig) (Provider, error) { + initializer, has := providerInitializers[pc.typ] + if !has { + return nil, errors.New("unknown provider type: " + pc.typ) + } + return initializer.CreateProvider(pc) +} + +type Provider interface { + GetProviderType() string + GetEmbedding( + queryString string, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(emb []float64, err error)) error +} diff --git a/plugins/wasm-go/extensions/ai-cache/embedding/weaviate.go b/plugins/wasm-go/extensions/ai-cache/embedding/weaviate.go new file mode 100644 index 0000000000..b26d9cea8d --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/embedding/weaviate.go @@ -0,0 +1,27 @@ +package embedding + +// import ( +// "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +// ) + +// const ( +// weaviateURL = "172.17.0.1:8081" +// ) + +// type weaviateProviderInitializer struct { +// } + +// func (d *weaviateProviderInitializer) ValidateConfig(config ProviderConfig) error { +// return nil +// } + +// func (d *weaviateProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) { +// return &DSProvider{ +// config: config, +// client: wrapper.NewClusterClient(wrapper.DnsCluster{ +// ServiceName: config.ServiceName, +// Port: dashScopePort, +// Domain: dashScopeDomain, +// }), +// }, nil +// } diff --git a/plugins/wasm-go/extensions/ai-cache/go.mod b/plugins/wasm-go/extensions/ai-cache/go.mod index c9630cfb8a..e4aae265e0 100644 --- a/plugins/wasm-go/extensions/ai-cache/go.mod +++ b/plugins/wasm-go/extensions/ai-cache/go.mod @@ -7,17 +7,18 @@ go 1.19 replace github.com/alibaba/higress/plugins/wasm-go => ../.. require ( - github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240528060522-53bccf89f441 + github.com/alibaba/higress/plugins/wasm-go v1.4.2 github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f - github.com/tidwall/gjson v1.14.3 + github.com/tidwall/gjson v1.17.3 github.com/tidwall/resp v0.1.1 - github.com/tidwall/sjson v1.2.5 +// github.com/weaviate/weaviate-go-client/v4 v4.15.1 ) require ( - github.com/google/uuid v1.3.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect github.com/magefile/mage v1.14.0 // indirect + github.com/stretchr/testify v1.9.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect ) diff --git a/plugins/wasm-go/extensions/ai-cache/go.sum b/plugins/wasm-go/extensions/ai-cache/go.sum index 8246b4de5e..7ada0c8b70 100644 --- a/plugins/wasm-go/extensions/ai-cache/go.sum +++ b/plugins/wasm-go/extensions/ai-cache/go.sum @@ -1,24 +1,21 @@ 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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.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-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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg= github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/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.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.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/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/plugins/wasm-go/extensions/ai-cache/main.go b/plugins/wasm-go/extensions/ai-cache/main.go index 7886d5698f..1aca29f0ec 100644 --- a/plugins/wasm-go/extensions/ai-cache/main.go +++ b/plugins/wasm-go/extensions/ai-cache/main.go @@ -1,33 +1,33 @@ -// File generated by hgctl. Modify as required. -// See: https://higress.io/zh-cn/docs/user/wasm-go#2-%E7%BC%96%E5%86%99-maingo-%E6%96%87%E4%BB%B6 - +// 这个文件中主要将OnHttpRequestHeaders、OnHttpRequestBody、OnHttpResponseHeaders、OnHttpResponseBody这四个函数实现 +// 其中的缓存思路调用cache.go中的逻辑,然后cache.go中的逻辑会调用textEmbeddingProvider和vectorStoreProvider中的逻辑(实例) package main import ( - "errors" - "fmt" "strings" + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/config" "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/tidwall/resp" ) const ( - CacheKeyContextKey = "cacheKey" - CacheContentContextKey = "cacheContent" - PartialMessageContextKey = "partialMessage" - ToolCallsContextKey = "toolCalls" - StreamContextKey = "stream" - DefaultCacheKeyPrefix = "higress-ai-cache:" - SkipCacheHeader = "x-higress-skip-ai-cache" + PLUGIN_NAME = "ai-cache" + CACHE_KEY_CONTEXT_KEY = "cacheKey" + CACHE_KEY_EMBEDDING_KEY = "cacheKeyEmbedding" + CACHE_CONTENT_CONTEXT_KEY = "cacheContent" + PARTIAL_MESSAGE_CONTEXT_KEY = "partialMessage" + TOOL_CALLS_CONTEXT_KEY = "toolCalls" + STREAM_CONTEXT_KEY = "stream" + SKIP_CACHE_HEADER = "x-higress-skip-ai-cache" + ERROR_PARTIAL_MESSAGE_KEY = "errorPartialMessage" ) func main() { + // CreateClient() wrapper.SetCtx( - "ai-cache", + PLUGIN_NAME, wrapper.ParseConfigBy(parseConfig), wrapper.ProcessRequestHeadersBy(onHttpRequestHeaders), wrapper.ProcessRequestBodyBy(onHttpRequestBody), @@ -36,146 +36,26 @@ func main() { ) } -// @Name ai-cache -// @Category protocol -// @Phase AUTHN -// @Priority 10 -// @Title zh-CN AI Cache -// @Description zh-CN 大模型结果缓存 -// @IconUrl -// @Version 0.1.0 -// -// @Contact.name johnlanni -// @Contact.url -// @Contact.email -// -// @Example -// redis: -// serviceName: my-redis.dns -// timeout: 2000 -// cacheKeyFrom: -// requestBody: "messages.@reverse.0.content" -// cacheValueFrom: -// responseBody: "choices.0.message.content" -// cacheStreamValueFrom: -// responseBody: "choices.0.delta.content" -// returnResponseTemplate: | -// {"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}} -// returnStreamResponseTemplate: | -// 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}} -// -// data:[DONE] -// -// @End - -type RedisInfo struct { - // @Title zh-CN redis 服务名称 - // @Description zh-CN 带服务类型的完整 FQDN 名称,例如 my-redis.dns、redis.my-ns.svc.cluster.local - ServiceName string `required:"true" yaml:"serviceName" json:"serviceName"` - // @Title zh-CN redis 服务端口 - // @Description zh-CN 默认值为6379 - ServicePort int `required:"false" yaml:"servicePort" json:"servicePort"` - // @Title zh-CN 用户名 - // @Description zh-CN 登陆 redis 的用户名,非必填 - Username string `required:"false" yaml:"username" json:"username"` - // @Title zh-CN 密码 - // @Description zh-CN 登陆 redis 的密码,非必填,可以只填密码 - Password string `required:"false" yaml:"password" json:"password"` - // @Title zh-CN 请求超时 - // @Description zh-CN 请求 redis 的超时时间,单位为毫秒。默认值是1000,即1秒 - Timeout int `required:"false" yaml:"timeout" json:"timeout"` -} - -type KVExtractor struct { - // @Title zh-CN 从请求 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 - RequestBody string `required:"false" yaml:"requestBody" json:"requestBody"` - // @Title zh-CN 从响应 Body 中基于 [GJSON PATH](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) 语法提取字符串 - ResponseBody string `required:"false" yaml:"responseBody" json:"responseBody"` -} - -type PluginConfig struct { - // @Title zh-CN Redis 地址信息 - // @Description zh-CN 用于存储缓存结果的 Redis 地址 - RedisInfo RedisInfo `required:"true" yaml:"redis" json:"redis"` - // @Title zh-CN 缓存 key 的来源 - // @Description zh-CN 往 redis 里存时,使用的 key 的提取方式 - CacheKeyFrom KVExtractor `required:"true" yaml:"cacheKeyFrom" json:"cacheKeyFrom"` - // @Title zh-CN 缓存 value 的来源 - // @Description zh-CN 往 redis 里存时,使用的 value 的提取方式 - CacheValueFrom KVExtractor `required:"true" yaml:"cacheValueFrom" json:"cacheValueFrom"` - // @Title zh-CN 流式响应下,缓存 value 的来源 - // @Description zh-CN 往 redis 里存时,使用的 value 的提取方式 - CacheStreamValueFrom KVExtractor `required:"true" yaml:"cacheStreamValueFrom" json:"cacheStreamValueFrom"` - // @Title zh-CN 返回 HTTP 响应的模版 - // @Description zh-CN 用 %s 标记需要被 cache value 替换的部分 - ReturnResponseTemplate string `required:"true" yaml:"returnResponseTemplate" json:"returnResponseTemplate"` - // @Title zh-CN 返回流式 HTTP 响应的模版 - // @Description zh-CN 用 %s 标记需要被 cache value 替换的部分 - ReturnStreamResponseTemplate string `required:"true" yaml:"returnStreamResponseTemplate" json:"returnStreamResponseTemplate"` - // @Title zh-CN 缓存的过期时间 - // @Description zh-CN 单位是秒,默认值为0,即永不过期 - CacheTTL int `required:"false" yaml:"cacheTTL" json:"cacheTTL"` - // @Title zh-CN Redis缓存Key的前缀 - // @Description zh-CN 默认值是"higress-ai-cache:" - CacheKeyPrefix string `required:"false" yaml:"cacheKeyPrefix" json:"cacheKeyPrefix"` - redisClient wrapper.RedisClient `yaml:"-" json:"-"` -} - -func parseConfig(json gjson.Result, c *PluginConfig, log wrapper.Log) error { - c.RedisInfo.ServiceName = json.Get("redis.serviceName").String() - if c.RedisInfo.ServiceName == "" { - return errors.New("redis service name must not by empty") - } - c.RedisInfo.ServicePort = int(json.Get("redis.servicePort").Int()) - if c.RedisInfo.ServicePort == 0 { - if strings.HasSuffix(c.RedisInfo.ServiceName, ".static") { - // use default logic port which is 80 for static service - c.RedisInfo.ServicePort = 80 - } else { - c.RedisInfo.ServicePort = 6379 - } - } - c.RedisInfo.Username = json.Get("redis.username").String() - c.RedisInfo.Password = json.Get("redis.password").String() - c.RedisInfo.Timeout = int(json.Get("redis.timeout").Int()) - if c.RedisInfo.Timeout == 0 { - c.RedisInfo.Timeout = 1000 - } - c.CacheKeyFrom.RequestBody = json.Get("cacheKeyFrom.requestBody").String() - if c.CacheKeyFrom.RequestBody == "" { - c.CacheKeyFrom.RequestBody = "messages.@reverse.0.content" - } - c.CacheValueFrom.ResponseBody = json.Get("cacheValueFrom.responseBody").String() - if c.CacheValueFrom.ResponseBody == "" { - c.CacheValueFrom.ResponseBody = "choices.0.message.content" - } - c.CacheStreamValueFrom.ResponseBody = json.Get("cacheStreamValueFrom.responseBody").String() - if c.CacheStreamValueFrom.ResponseBody == "" { - c.CacheStreamValueFrom.ResponseBody = "choices.0.delta.content" - } - c.ReturnResponseTemplate = json.Get("returnResponseTemplate").String() - if c.ReturnResponseTemplate == "" { - c.ReturnResponseTemplate = `{"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.ReturnStreamResponseTemplate = json.Get("returnStreamResponseTemplate").String() - if c.ReturnStreamResponseTemplate == "" { - c.ReturnStreamResponseTemplate = `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" +func parseConfig(json gjson.Result, c *config.PluginConfig, log wrapper.Log) error { + // config.EmbeddingProviderConfig.FromJson(json.Get("embeddingProvider")) + // config.VectorDatabaseProviderConfig.FromJson(json.Get("vectorBaseProvider")) + // config.RedisConfig.FromJson(json.Get("redis")) + c.FromJson(json, log) + if err := c.Validate(); err != nil { + return err } - c.CacheKeyPrefix = json.Get("cacheKeyPrefix").String() - if c.CacheKeyPrefix == "" { - c.CacheKeyPrefix = DefaultCacheKeyPrefix + // Note that initializing the client during the parseConfig phase may cause errors, such as Redis not being usable in Docker Compose. + if err := c.Complete(log); err != nil { + log.Errorf("complete config failed: %v", err) + return err } - c.redisClient = wrapper.NewRedisClusterClient(wrapper.FQDNCluster{ - FQDN: c.RedisInfo.ServiceName, - Port: int64(c.RedisInfo.ServicePort), - }) - return c.redisClient.Init(c.RedisInfo.Username, c.RedisInfo.Password, int64(c.RedisInfo.Timeout)) + return nil } -func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action { - skipCache, _ := proxywasm.GetHttpRequestHeader(SkipCacheHeader) +func onHttpRequestHeaders(ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log) types.Action { + skipCache, _ := proxywasm.GetHttpRequestHeader(SKIP_CACHE_HEADER) if skipCache == "on" { - ctx.SetContext(SkipCacheHeader, struct{}{}) + ctx.SetContext(SKIP_CACHE_HEADER, struct{}{}) ctx.DontReadRequestBody() return types.ActionContinue } @@ -185,199 +65,123 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrap return types.ActionContinue } if !strings.Contains(contentType, "application/json") { - log.Warnf("content is not json, can't process:%s", contentType) + log.Warnf("content is not json, can't process: %s", contentType) ctx.DontReadRequestBody() return types.ActionContinue } - proxywasm.RemoveHttpRequestHeader("Accept-Encoding") + _ = proxywasm.RemoveHttpRequestHeader("Accept-Encoding") // The request has a body and requires delaying the header transmission until a cache miss occurs, // at which point the header should be sent. return types.HeaderStopIteration } -func TrimQuote(source string) string { - return strings.Trim(source, `"`) -} +func onHttpRequestBody(ctx wrapper.HttpContext, c config.PluginConfig, body []byte, log wrapper.Log) types.Action { -func onHttpRequestBody(ctx wrapper.HttpContext, config PluginConfig, body []byte, log wrapper.Log) types.Action { bodyJson := gjson.ParseBytes(body) // TODO: It may be necessary to support stream mode determination for different LLM providers. stream := false if bodyJson.Get("stream").Bool() { stream = true - ctx.SetContext(StreamContextKey, struct{}{}) - } else if ctx.GetContext(StreamContextKey) != nil { - stream = true + ctx.SetContext(STREAM_CONTEXT_KEY, struct{}{}) + } + + var key string + if c.CacheKeyStrategy == config.CACHE_KEY_STRATEGY_LAST_QUESTION { + log.Debugf("[onHttpRequestBody] cache key strategy is last question, cache key from: %s", c.CacheKeyFrom) + key = bodyJson.Get(c.CacheKeyFrom).String() + } else if c.CacheKeyStrategy == config.CACHE_KEY_STRATEGY_ALL_QUESTIONS { + log.Debugf("[onHttpRequestBody] cache key strategy is all questions, cache key from: messages") + messages := bodyJson.Get("messages").Array() + var userMessages []string + for _, msg := range messages { + if msg.Get("role").String() == "user" { + userMessages = append(userMessages, msg.Get("content").String()) + } + } + key = strings.Join(userMessages, "\n") + } else if c.CacheKeyStrategy == config.CACHE_KEY_STRATEGY_DISABLED { + log.Info("[onHttpRequestBody] cache key strategy is disabled") + ctx.DontReadRequestBody() + return types.ActionContinue + } else { + log.Warnf("[onHttpRequestBody] unknown cache key strategy: %s", c.CacheKeyStrategy) + ctx.DontReadRequestBody() + return types.ActionContinue } - key := TrimQuote(bodyJson.Get(config.CacheKeyFrom.RequestBody).Raw) + + ctx.SetContext(CACHE_KEY_CONTEXT_KEY, key) + log.Debugf("[onHttpRequestBody] key: %s", key) if key == "" { - log.Debug("parse key from request body failed") + log.Debug("[onHttpRequestBody] parse key from request body failed") + ctx.DontReadResponseBody() return types.ActionContinue } - ctx.SetContext(CacheKeyContextKey, key) - err := config.redisClient.Get(config.CacheKeyPrefix+key, func(response resp.Value) { - if err := response.Error(); err != nil { - log.Errorf("redis get key:%s failed, err:%v", key, err) - proxywasm.ResumeHttpRequest() - return - } - if response.IsNull() { - log.Debugf("cache miss, key:%s", key) - proxywasm.ResumeHttpRequest() - return - } - log.Debugf("cache hit, key:%s", key) - ctx.SetContext(CacheKeyContextKey, nil) - if !stream { - proxywasm.SendHttpResponseWithDetail(200, "ai-cache.hit", [][2]string{{"content-type", "application/json; charset=utf-8"}}, []byte(fmt.Sprintf(config.ReturnResponseTemplate, response.String())), -1) - } else { - proxywasm.SendHttpResponseWithDetail(200, "ai-cache.hit", [][2]string{{"content-type", "text/event-stream; charset=utf-8"}}, []byte(fmt.Sprintf(config.ReturnStreamResponseTemplate, response.String())), -1) - } - }) - if err != nil { - log.Error("redis access failed") + + if err := CheckCacheForKey(key, ctx, c, log, stream, true); err != nil { + log.Errorf("[onHttpRequestBody] check cache for key: %s failed, error: %v", key, err) return types.ActionContinue } - return types.ActionPause -} -func processSSEMessage(ctx wrapper.HttpContext, config PluginConfig, sseMessage string, log wrapper.Log) string { - subMessages := strings.Split(sseMessage, "\n") - var message string - for _, msg := range subMessages { - if strings.HasPrefix(msg, "data:") { - message = msg - break - } - } - if len(message) < 6 { - log.Errorf("invalid message:%s", message) - return "" - } - // skip the prefix "data:" - bodyJson := message[5:] - if gjson.Get(bodyJson, config.CacheStreamValueFrom.ResponseBody).Exists() { - tempContentI := ctx.GetContext(CacheContentContextKey) - if tempContentI == nil { - content := TrimQuote(gjson.Get(bodyJson, config.CacheStreamValueFrom.ResponseBody).Raw) - ctx.SetContext(CacheContentContextKey, content) - return content - } - append := TrimQuote(gjson.Get(bodyJson, config.CacheStreamValueFrom.ResponseBody).Raw) - content := tempContentI.(string) + append - ctx.SetContext(CacheContentContextKey, content) - return content - } else if gjson.Get(bodyJson, "choices.0.delta.content.tool_calls").Exists() { - // TODO: compatible with other providers - ctx.SetContext(ToolCallsContextKey, struct{}{}) - return "" - } - log.Debugf("unknown message:%s", bodyJson) - return "" + return types.ActionPause } -func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log wrapper.Log) types.Action { - skipCache := ctx.GetContext(SkipCacheHeader) +func onHttpResponseHeaders(ctx wrapper.HttpContext, c config.PluginConfig, log wrapper.Log) types.Action { + skipCache := ctx.GetContext(SKIP_CACHE_HEADER) if skipCache != nil { ctx.DontReadResponseBody() return types.ActionContinue } contentType, _ := proxywasm.GetHttpResponseHeader("content-type") if strings.Contains(contentType, "text/event-stream") { - ctx.SetContext(StreamContextKey, struct{}{}) + ctx.SetContext(STREAM_CONTEXT_KEY, struct{}{}) } + + if ctx.GetContext(ERROR_PARTIAL_MESSAGE_KEY) != nil { + ctx.DontReadResponseBody() + return types.ActionContinue + } + return types.ActionContinue } -func onHttpResponseBody(ctx wrapper.HttpContext, config PluginConfig, chunk []byte, isLastChunk bool, log wrapper.Log) []byte { - if ctx.GetContext(ToolCallsContextKey) != nil { - // we should not cache tool call result +func onHttpResponseBody(ctx wrapper.HttpContext, c config.PluginConfig, chunk []byte, isLastChunk bool, log wrapper.Log) []byte { + log.Debugf("[onHttpResponseBody] is last chunk: %v", isLastChunk) + log.Debugf("[onHttpResponseBody] chunk: %s", string(chunk)) + + if ctx.GetContext(TOOL_CALLS_CONTEXT_KEY) != nil { return chunk } - keyI := ctx.GetContext(CacheKeyContextKey) - if keyI == nil { + + key := ctx.GetContext(CACHE_KEY_CONTEXT_KEY) + if key == nil { + log.Debug("[onHttpResponseBody] key is nil, skip cache") return chunk } + if !isLastChunk { - stream := ctx.GetContext(StreamContextKey) - if stream == nil { - tempContentI := ctx.GetContext(CacheContentContextKey) - if tempContentI == nil { - ctx.SetContext(CacheContentContextKey, chunk) - return chunk - } - tempContent := tempContentI.([]byte) - tempContent = append(tempContent, chunk...) - ctx.SetContext(CacheContentContextKey, tempContent) - } else { - var partialMessage []byte - partialMessageI := ctx.GetContext(PartialMessageContextKey) - if partialMessageI != nil { - partialMessage = append(partialMessageI.([]byte), chunk...) - } else { - partialMessage = chunk - } - messages := strings.Split(string(partialMessage), "\n\n") - for i, msg := range messages { - if i < len(messages)-1 { - // process complete message - processSSEMessage(ctx, config, msg, log) - } - } - if !strings.HasSuffix(string(partialMessage), "\n\n") { - ctx.SetContext(PartialMessageContextKey, []byte(messages[len(messages)-1])) - } else { - ctx.SetContext(PartialMessageContextKey, nil) - } + if err := handleNonLastChunk(ctx, c, chunk, log); err != nil { + log.Errorf("[onHttpResponseBody] handle non last chunk failed, error: %v", err) + // Set an empty struct in the context to indicate an error in processing the partial message + ctx.SetContext(ERROR_PARTIAL_MESSAGE_KEY, struct{}{}) } return chunk } - // last chunk - key := keyI.(string) - stream := ctx.GetContext(StreamContextKey) + + stream := ctx.GetContext(STREAM_CONTEXT_KEY) var value string + var err error if stream == nil { - var body []byte - tempContentI := ctx.GetContext(CacheContentContextKey) - if tempContentI != nil { - body = append(tempContentI.([]byte), chunk...) - } else { - body = chunk - } - bodyJson := gjson.ParseBytes(body) - - value = TrimQuote(bodyJson.Get(config.CacheValueFrom.ResponseBody).Raw) - if value == "" { - log.Warnf("parse value from response body failded, body:%s", body) - return chunk - } + value, err = processNonStreamLastChunk(ctx, c, chunk, log) } else { - if len(chunk) > 0 { - var lastMessage []byte - partialMessageI := ctx.GetContext(PartialMessageContextKey) - if partialMessageI != nil { - lastMessage = append(partialMessageI.([]byte), chunk...) - } else { - lastMessage = chunk - } - if !strings.HasSuffix(string(lastMessage), "\n\n") { - log.Warnf("invalid lastMessage:%s", lastMessage) - return chunk - } - // remove the last \n\n - lastMessage = lastMessage[:len(lastMessage)-2] - value = processSSEMessage(ctx, config, string(lastMessage), log) - } else { - tempContentI := ctx.GetContext(CacheContentContextKey) - if tempContentI == nil { - return chunk - } - value = tempContentI.(string) - } + value, err = processStreamLastChunk(ctx, c, chunk, log) } - config.redisClient.Set(config.CacheKeyPrefix+key, value, nil) - if config.CacheTTL != 0 { - config.redisClient.Expire(config.CacheKeyPrefix+key, config.CacheTTL, nil) + + if err != nil { + log.Errorf("[onHttpResponseBody] process last chunk failed, error: %v", err) + return chunk } + + cacheResponse(ctx, c, key.(string), value, log) + uploadEmbeddingAndAnswer(ctx, c, key.(string), value, log) return chunk } diff --git a/plugins/wasm-go/extensions/ai-cache/util.go b/plugins/wasm-go/extensions/ai-cache/util.go new file mode 100644 index 0000000000..983dfbb25a --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/util.go @@ -0,0 +1,155 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/alibaba/higress/plugins/wasm-go/extensions/ai-cache/config" + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" +) + +func handleNonLastChunk(ctx wrapper.HttpContext, c config.PluginConfig, chunk []byte, log wrapper.Log) error { + stream := ctx.GetContext(STREAM_CONTEXT_KEY) + err := error(nil) + if stream == nil { + err = handleNonStreamChunk(ctx, c, chunk, log) + } else { + err = handleStreamChunk(ctx, c, chunk, log) + } + return err +} + +func handleNonStreamChunk(ctx wrapper.HttpContext, c config.PluginConfig, chunk []byte, log wrapper.Log) error { + tempContentI := ctx.GetContext(CACHE_CONTENT_CONTEXT_KEY) + if tempContentI == nil { + ctx.SetContext(CACHE_CONTENT_CONTEXT_KEY, chunk) + return nil + } + tempContent := tempContentI.([]byte) + tempContent = append(tempContent, chunk...) + ctx.SetContext(CACHE_CONTENT_CONTEXT_KEY, tempContent) + return nil +} + +func handleStreamChunk(ctx wrapper.HttpContext, c config.PluginConfig, chunk []byte, log wrapper.Log) error { + var partialMessage []byte + partialMessageI := ctx.GetContext(PARTIAL_MESSAGE_CONTEXT_KEY) + log.Debugf("[handleStreamChunk] cache content: %v", ctx.GetContext(CACHE_CONTENT_CONTEXT_KEY)) + if partialMessageI != nil { + partialMessage = append(partialMessageI.([]byte), chunk...) + } else { + partialMessage = chunk + } + messages := strings.Split(string(partialMessage), "\n\n") + for i, msg := range messages { + if i < len(messages)-1 { + _, err := processSSEMessage(ctx, c, msg, log) + if err != nil { + return fmt.Errorf("[handleStreamChunk] processSSEMessage failed, error: %v", err) + } + } + } + if !strings.HasSuffix(string(partialMessage), "\n\n") { + ctx.SetContext(PARTIAL_MESSAGE_CONTEXT_KEY, []byte(messages[len(messages)-1])) + } else { + ctx.SetContext(PARTIAL_MESSAGE_CONTEXT_KEY, nil) + } + return nil +} + +func processNonStreamLastChunk(ctx wrapper.HttpContext, c config.PluginConfig, chunk []byte, log wrapper.Log) (string, error) { + var body []byte + tempContentI := ctx.GetContext(CACHE_CONTENT_CONTEXT_KEY) + if tempContentI != nil { + body = append(tempContentI.([]byte), chunk...) + } else { + body = chunk + } + bodyJson := gjson.ParseBytes(body) + value := bodyJson.Get(c.CacheValueFrom).String() + if strings.TrimSpace(value) == "" { + return "", fmt.Errorf("[processNonStreamLastChunk] parse value from response body failed, body:%s", body) + } + return value, nil +} + +func processStreamLastChunk(ctx wrapper.HttpContext, c config.PluginConfig, chunk []byte, log wrapper.Log) (string, error) { + if len(chunk) > 0 { + var lastMessage []byte + partialMessageI := ctx.GetContext(PARTIAL_MESSAGE_CONTEXT_KEY) + if partialMessageI != nil { + lastMessage = append(partialMessageI.([]byte), chunk...) + } else { + lastMessage = chunk + } + if !strings.HasSuffix(string(lastMessage), "\n\n") { + return "", fmt.Errorf("[processStreamLastChunk] invalid lastMessage:%s", lastMessage) + } + lastMessage = lastMessage[:len(lastMessage)-2] + value, err := processSSEMessage(ctx, c, string(lastMessage), log) + if err != nil { + return "", fmt.Errorf("[processStreamLastChunk] processSSEMessage failed, error: %v", err) + } + return value, nil + } + tempContentI := ctx.GetContext(CACHE_CONTENT_CONTEXT_KEY) + if tempContentI == nil { + return "", nil + } + return tempContentI.(string), nil +} + +func processSSEMessage(ctx wrapper.HttpContext, c config.PluginConfig, sseMessage string, log wrapper.Log) (string, error) { + subMessages := strings.Split(sseMessage, "\n") + var message string + for _, msg := range subMessages { + if strings.HasPrefix(msg, "data:") { + message = msg + break + } + } + if len(message) < 6 { + return "", fmt.Errorf("[processSSEMessage] invalid message: %s", message) + } + + // skip the prefix "data:" + bodyJson := message[5:] + + if strings.TrimSpace(bodyJson) == "[DONE]" { + return "", nil + } + + // Extract values from JSON fields + responseBody := gjson.Get(bodyJson, c.CacheStreamValueFrom) + toolCalls := gjson.Get(bodyJson, c.CacheToolCallsFrom) + + if toolCalls.Exists() { + // TODO: Temporarily store the tool_calls value in the context for processing + ctx.SetContext(TOOL_CALLS_CONTEXT_KEY, toolCalls.String()) + } + + // Check if the ResponseBody field exists + if !responseBody.Exists() { + if ctx.GetContext(CACHE_CONTENT_CONTEXT_KEY) != nil { + log.Debugf("[processSSEMessage] unable to extract content from message; cache content is not nil: %s", message) + return "", nil + } + return "", fmt.Errorf("[processSSEMessage] unable to extract content from message; cache content is nil: %s", message) + } else { + tempContentI := ctx.GetContext(CACHE_CONTENT_CONTEXT_KEY) + + // If there is no content in the cache, initialize and set the content + if tempContentI == nil { + content := responseBody.String() + ctx.SetContext(CACHE_CONTENT_CONTEXT_KEY, content) + return content, nil + } + + // Update the content in the cache + appendMsg := responseBody.String() + content := tempContentI.(string) + appendMsg + ctx.SetContext(CACHE_CONTENT_CONTEXT_KEY, content) + return content, nil + } +} diff --git a/plugins/wasm-go/extensions/ai-cache/vector/dashvector.go b/plugins/wasm-go/extensions/ai-cache/vector/dashvector.go new file mode 100644 index 0000000000..7bdb0a76d0 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/vector/dashvector.go @@ -0,0 +1,256 @@ +package vector + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +) + +type dashVectorProviderInitializer struct { +} + +func (d *dashVectorProviderInitializer) ValidateConfig(config ProviderConfig) error { + if len(config.apiKey) == 0 { + return errors.New("[DashVector] apiKey is required") + } + if len(config.collectionID) == 0 { + return errors.New("[DashVector] collectionID is required") + } + if len(config.serviceName) == 0 { + return errors.New("[DashVector] serviceName is required") + } + if len(config.serviceHost) == 0 { + return errors.New("[DashVector] serviceHost is required") + } + return nil +} + +func (d *dashVectorProviderInitializer) CreateProvider(config ProviderConfig) (Provider, error) { + return &DvProvider{ + config: config, + client: wrapper.NewClusterClient(wrapper.FQDNCluster{ + FQDN: config.serviceName, + Host: config.serviceHost, + Port: int64(config.servicePort), + }), + }, nil +} + +type DvProvider struct { + config ProviderConfig + client wrapper.HttpClient +} + +func (d *DvProvider) GetProviderType() string { + return PROVIDER_TYPE_DASH_VECTOR +} + +// type embeddingRequest struct { +// Model string `json:"model"` +// Input input `json:"input"` +// Parameters params `json:"parameters"` +// } + +// type params struct { +// TextType string `json:"text_type"` +// } + +// type input struct { +// Texts []string `json:"texts"` +// } + +// queryResponse 定义查询响应的结构 +type queryResponse struct { + Code int `json:"code"` + RequestID string `json:"request_id"` + Message string `json:"message"` + Output []result `json:"output"` +} + +// queryRequest 定义查询请求的结构 +type queryRequest struct { + Vector []float64 `json:"vector"` + TopK int `json:"topk"` + IncludeVector bool `json:"include_vector"` +} + +// result 定义查询结果的结构 +type result struct { + ID string `json:"id"` + Vector []float64 `json:"vector,omitempty"` // omitempty 使得如果 vector 是空,它将不会被序列化 + Fields map[string]interface{} `json:"fields"` + Score float64 `json:"score"` +} + +func (d *DvProvider) constructEmbeddingQueryParameters(vector []float64) (string, []byte, [][2]string, error) { + url := fmt.Sprintf("/v1/collections/%s/query", d.config.collectionID) + + requestData := queryRequest{ + Vector: vector, + TopK: d.config.topK, + IncludeVector: false, + } + + requestBody, err := json.Marshal(requestData) + if err != nil { + return "", nil, nil, err + } + + header := [][2]string{ + {"Content-Type", "application/json"}, + {"dashvector-auth-token", d.config.apiKey}, + } + + return url, requestBody, header, nil +} + +func (d *DvProvider) parseQueryResponse(responseBody []byte) (queryResponse, error) { + var queryResp queryResponse + err := json.Unmarshal(responseBody, &queryResp) + if err != nil { + return queryResponse{}, err + } + return queryResp, nil +} + +func (d *DvProvider) QueryEmbedding( + emb []float64, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(results []QueryResult, ctx wrapper.HttpContext, log wrapper.Log, err error)) error { + url, body, headers, err := d.constructEmbeddingQueryParameters(emb) + log.Debugf("url:%s, body:%s, headers:%v", url, string(body), headers) + if err != nil { + err = fmt.Errorf("failed to construct embedding query parameters: %v", err) + return err + } + + err = d.client.Post(url, headers, body, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + err = nil + if statusCode != http.StatusOK { + err = fmt.Errorf("failed to query embedding: %d", statusCode) + callback(nil, ctx, log, err) + return + } + log.Debugf("query embedding response: %d, %s", statusCode, responseBody) + results, err := d.ParseQueryResponse(responseBody, ctx, log) + if err != nil { + err = fmt.Errorf("failed to parse query response: %v", err) + } + callback(results, ctx, log, err) + }, + d.config.timeout) + if err != nil { + err = fmt.Errorf("failed to query embedding: %v", err) + } + return err +} + +func getStringValue(fields map[string]interface{}, key string) string { + if val, ok := fields[key]; ok { + return val.(string) + } + return "" +} + +func (d *DvProvider) ParseQueryResponse(responseBody []byte, ctx wrapper.HttpContext, log wrapper.Log) ([]QueryResult, error) { + resp, err := d.parseQueryResponse(responseBody) + if err != nil { + return nil, err + } + + if len(resp.Output) == 0 { + return nil, errors.New("no query results found in response") + } + + results := make([]QueryResult, 0, len(resp.Output)) + + for _, output := range resp.Output { + result := QueryResult{ + Text: getStringValue(output.Fields, "query"), + Embedding: output.Vector, + Score: output.Score, + Answer: getStringValue(output.Fields, "answer"), + } + results = append(results, result) + } + + return results, nil +} + +type document struct { + Vector []float64 `json:"vector"` + Fields map[string]string `json:"fields"` +} + +type insertRequest struct { + Docs []document `json:"docs"` +} + +func (d *DvProvider) constructUploadParameters(emb []float64, queryString string, answer string) (string, []byte, [][2]string, error) { + url := "/v1/collections/" + d.config.collectionID + "/docs" + + doc := document{ + Vector: emb, + Fields: map[string]string{ + "query": queryString, + "answer": answer, + }, + } + + requestBody, err := json.Marshal(insertRequest{Docs: []document{doc}}) + if err != nil { + return "", nil, nil, err + } + + header := [][2]string{ + {"Content-Type", "application/json"}, + {"dashvector-auth-token", d.config.apiKey}, + } + + return url, requestBody, header, err +} + +func (d *DvProvider) UploadEmbedding(queryString string, queryEmb []float64, ctx wrapper.HttpContext, log wrapper.Log, callback func(ctx wrapper.HttpContext, log wrapper.Log, err error)) error { + url, body, headers, err := d.constructUploadParameters(queryEmb, queryString, "") + if err != nil { + return err + } + err = d.client.Post( + url, + headers, + body, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + log.Debugf("statusCode:%d, responseBody:%s", statusCode, string(responseBody)) + if statusCode != http.StatusOK { + err = fmt.Errorf("failed to upload embedding: %d", statusCode) + } + callback(ctx, log, err) + }, + d.config.timeout) + return err +} + +func (d *DvProvider) UploadAnswerAndEmbedding(queryString string, queryEmb []float64, queryAnswer string, ctx wrapper.HttpContext, log wrapper.Log, callback func(ctx wrapper.HttpContext, log wrapper.Log, err error)) error { + url, body, headers, err := d.constructUploadParameters(queryEmb, queryString, queryAnswer) + if err != nil { + return err + } + err = d.client.Post( + url, + headers, + body, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + log.Debugf("statusCode:%d, responseBody:%s", statusCode, string(responseBody)) + if statusCode != http.StatusOK { + err = fmt.Errorf("failed to upload embedding: %d", statusCode) + } + callback(ctx, log, err) + }, + d.config.timeout) + return err +} diff --git a/plugins/wasm-go/extensions/ai-cache/vector/provider.go b/plugins/wasm-go/extensions/ai-cache/vector/provider.go new file mode 100644 index 0000000000..a04123a166 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/vector/provider.go @@ -0,0 +1,167 @@ +package vector + +import ( + "errors" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" + "github.com/tidwall/gjson" +) + +const ( + PROVIDER_TYPE_DASH_VECTOR = "dashvector" + PROVIDER_TYPE_CHROMA = "chroma" +) + +type providerInitializer interface { + ValidateConfig(ProviderConfig) error + CreateProvider(ProviderConfig) (Provider, error) +} + +var ( + providerInitializers = map[string]providerInitializer{ + PROVIDER_TYPE_DASH_VECTOR: &dashVectorProviderInitializer{}, + // PROVIDER_TYPE_CHROMA: &chromaProviderInitializer{}, + } +) + +// QueryResult 定义通用的查询结果的结构体 +type QueryResult struct { + Text string // 相似的文本 + Embedding []float64 // 相似文本的向量 + Score float64 // 文本的向量相似度或距离等度量 + Answer string // 相似文本对应的LLM生成的回答 +} + +type Provider interface { + GetProviderType() string +} + +type EmbeddingQuerier interface { + QueryEmbedding( + emb []float64, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(results []QueryResult, ctx wrapper.HttpContext, log wrapper.Log, err error)) error +} + +type EmbeddingUploader interface { + UploadEmbedding( + queryString string, + queryEmb []float64, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(ctx wrapper.HttpContext, log wrapper.Log, err error)) error +} + +type AnswerAndEmbeddingUploader interface { + UploadAnswerAndEmbedding( + queryString string, + queryEmb []float64, + answer string, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(ctx wrapper.HttpContext, log wrapper.Log, err error)) error +} + +type StringQuerier interface { + QueryString( + queryString string, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(results []QueryResult, ctx wrapper.HttpContext, log wrapper.Log, err error)) error +} + +type SimilarityThresholdProvider interface { + GetSimilarityThreshold() float64 +} + +type ProviderConfig struct { + // @Title zh-CN 向量存储服务提供者类型 + // @Description zh-CN 向量存储服务提供者类型,例如 dashvector、chroma + typ string + // @Title zh-CN 向量存储服务名称 + // @Description zh-CN 向量存储服务名称 + serviceName string + // @Title zh-CN 向量存储服务域名 + // @Description zh-CN 向量存储服务域名 + serviceHost string + // @Title zh-CN 向量存储服务端口 + // @Description zh-CN 向量存储服务端口 + servicePort int64 + // @Title zh-CN 向量存储服务 API Key + // @Description zh-CN 向量存储服务 API Key + apiKey string + // @Title zh-CN 返回TopK结果 + // @Description zh-CN 返回TopK结果,默认为 1 + topK int + // @Title zh-CN 请求超时 + // @Description zh-CN 请求向量存储服务的超时时间,单位为毫秒。默认值是10000,即10秒 + timeout uint32 + // @Title zh-CN DashVector 向量存储服务 Collection ID + // @Description zh-CN DashVector 向量存储服务 Collection ID + collectionID string + // @Title zh-CN 相似度度量阈值 + // @Description zh-CN 默认相似度度量阈值,默认为 1000。 + Threshold float64 + // @Title zh-CN 相似度度量比较方式 + // @Description zh-CN 相似度度量比较方式,默认为小于。 + // 相似度度量方式有 Cosine, DotProduct, Euclidean 等,前两者值越大相似度越高,后者值越小相似度越高。 + // 所以需要允许自定义比较方式,对于 Cosine 和 DotProduct 选择 gt,对于 Euclidean 则选择 lt。 + // 默认为 lt,所有条件包括 lt (less than,小于)、lte (less than or equal to,小等于)、gt (greater than,大于)、gte (greater than or equal to,大等于) + ThresholdRelation string +} + +func (c *ProviderConfig) GetProviderType() string { + return c.typ +} + +func (c *ProviderConfig) FromJson(json gjson.Result) { + c.typ = json.Get("type").String() + // DashVector + c.serviceName = json.Get("serviceName").String() + c.serviceHost = json.Get("serviceHost").String() + c.servicePort = int64(json.Get("servicePort").Int()) + if c.servicePort == 0 { + c.servicePort = 443 + } + c.apiKey = json.Get("apiKey").String() + c.collectionID = json.Get("collectionID").String() + c.topK = int(json.Get("topK").Int()) + if c.topK == 0 { + c.topK = 1 + } + c.timeout = uint32(json.Get("timeout").Int()) + if c.timeout == 0 { + c.timeout = 10000 + } + c.Threshold = json.Get("threshold").Float() + if c.Threshold == 0 { + c.Threshold = 1000 + } + c.ThresholdRelation = json.Get("thresholdRelation").String() + if c.ThresholdRelation == "" { + c.ThresholdRelation = "lt" + } +} + +func (c *ProviderConfig) Validate() error { + if c.typ == "" { + return errors.New("vector database service is required") + } + initializer, has := providerInitializers[c.typ] + if !has { + return errors.New("unknown vector database service provider type: " + c.typ) + } + if err := initializer.ValidateConfig(*c); err != nil { + return err + } + return nil +} + +func CreateProvider(pc ProviderConfig) (Provider, error) { + initializer, has := providerInitializers[pc.typ] + if !has { + return nil, errors.New("unknown provider type: " + pc.typ) + } + return initializer.CreateProvider(pc) +} diff --git a/plugins/wasm-go/extensions/ai-proxy/Makefile b/plugins/wasm-go/extensions/ai-proxy/Makefile new file mode 100644 index 0000000000..e5c7fa8de9 --- /dev/null +++ b/plugins/wasm-go/extensions/ai-proxy/Makefile @@ -0,0 +1,4 @@ +.DEFAULT: +build: + tinygo build -o ai-proxy.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer proxy_wasm_version_0_2_100' ./main.go + mv ai-proxy.wasm ../../../../docker-compose-test/ \ No newline at end of file diff --git a/plugins/wasm-go/extensions/ai-proxy/go.mod b/plugins/wasm-go/extensions/ai-proxy/go.mod index 7fed801fab..a5457b90f8 100644 --- a/plugins/wasm-go/extensions/ai-proxy/go.mod +++ b/plugins/wasm-go/extensions/ai-proxy/go.mod @@ -10,7 +10,7 @@ require ( github.com/alibaba/higress/plugins/wasm-go v0.0.0 github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f github.com/stretchr/testify v1.8.4 - github.com/tidwall/gjson v1.14.3 + github.com/tidwall/gjson v1.17.3 ) require ( diff --git a/plugins/wasm-go/extensions/ai-proxy/go.sum b/plugins/wasm-go/extensions/ai-proxy/go.sum index e5b8b79175..b2d63b5f4b 100644 --- a/plugins/wasm-go/extensions/ai-proxy/go.sum +++ b/plugins/wasm-go/extensions/ai-proxy/go.sum @@ -13,8 +13,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.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= diff --git a/plugins/wasm-go/extensions/ai-proxy/main.go b/plugins/wasm-go/extensions/ai-proxy/main.go index 9e0fafe179..7b19d03fc2 100644 --- a/plugins/wasm-go/extensions/ai-proxy/main.go +++ b/plugins/wasm-go/extensions/ai-proxy/main.go @@ -82,7 +82,8 @@ func onHttpRequestHeader(ctx wrapper.HttpContext, pluginConfig config.PluginConf providerConfig := pluginConfig.GetProviderConfig() if apiName == "" && !providerConfig.IsOriginal() { log.Debugf("[onHttpRequestHeader] unsupported path: %s", path.Path) - _ = util.SendResponse(404, "ai-proxy.unknown_api", util.MimeTypeTextPlain, "API not found: "+path.Path) + // _ = util.SendResponse(404, "ai-proxy.unknown_api", util.MimeTypeTextPlain, "API not found: "+path.Path) + log.Debugf("[onHttpRequestHeader] no send response") return types.ActionContinue } ctx.SetContext(ctxKeyApiName, apiName) diff --git a/plugins/wasm-go/extensions/request-block/Dockerfile b/plugins/wasm-go/extensions/request-block/Dockerfile new file mode 100644 index 0000000000..9b084e0596 --- /dev/null +++ b/plugins/wasm-go/extensions/request-block/Dockerfile @@ -0,0 +1,2 @@ +FROM scratch +COPY main.wasm plugin.wasm \ No newline at end of file diff --git a/plugins/wasm-go/extensions/request-block/Makefile b/plugins/wasm-go/extensions/request-block/Makefile new file mode 100644 index 0000000000..1210d6ec34 --- /dev/null +++ b/plugins/wasm-go/extensions/request-block/Makefile @@ -0,0 +1,4 @@ +.DEFAULT: +build: + tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' ./main.go + mv main.wasm ../../../../docker-compose-test/ \ No newline at end of file diff --git a/plugins/wasm-go/extensions/request-block/main.go b/plugins/wasm-go/extensions/request-block/main.go index 224d4b26d6..2a43b4df72 100644 --- a/plugins/wasm-go/extensions/request-block/main.go +++ b/plugins/wasm-go/extensions/request-block/main.go @@ -177,7 +177,9 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config RequestBlockConfig, lo } func onHttpRequestBody(ctx wrapper.HttpContext, config RequestBlockConfig, body []byte, log wrapper.Log) types.Action { + log.Infof("My request-block body: %s\n", string(body)) bodyStr := string(body) + if !config.caseSensitive { bodyStr = strings.ToLower(bodyStr) } diff --git a/plugins/wasm-go/go.mod b/plugins/wasm-go/go.mod index 999721f3f6..6373ff646e 100644 --- a/plugins/wasm-go/go.mod +++ b/plugins/wasm-go/go.mod @@ -7,7 +7,7 @@ require ( github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f github.com/stretchr/testify v1.8.4 - github.com/tidwall/gjson v1.14.3 + github.com/tidwall/gjson v1.17.3 github.com/tidwall/resp v0.1.1 ) diff --git a/plugins/wasm-go/go.sum b/plugins/wasm-go/go.sum index e726b100a5..f396d4d7d9 100644 --- a/plugins/wasm-go/go.sum +++ b/plugins/wasm-go/go.sum @@ -4,6 +4,12 @@ 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-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/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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg= github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo= github.com/magefile/mage v1.14.0 h1:6QDX3g6z1YvJ4olPhT1wksUcSa/V0a1B+pJb73fBjyo= @@ -14,6 +20,8 @@ github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcU github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.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= diff --git a/plugins/wasm-go/pkg/wrapper/redis_wrapper.go b/plugins/wasm-go/pkg/wrapper/redis_wrapper.go index 10aa9020bd..c619c3e191 100644 --- a/plugins/wasm-go/pkg/wrapper/redis_wrapper.go +++ b/plugins/wasm-go/pkg/wrapper/redis_wrapper.go @@ -235,10 +235,11 @@ func (c RedisClusterClient[C]) Set(key string, value interface{}, callback Redis func (c RedisClusterClient[C]) SetEx(key string, value interface{}, ttl int, callback RedisResponseCallback) error { args := make([]interface{}, 0) - args = append(args, "setex") + args = append(args, "set") args = append(args, key) - args = append(args, ttl) args = append(args, value) + args = append(args, "ex") + args = append(args, ttl) return RedisCall(c.cluster, respString(args), callback) } diff --git a/test/e2e/conformance/tests/go-wasm-ai-cache.go b/test/e2e/conformance/tests/go-wasm-ai-cache.go new file mode 100644 index 0000000000..30ac248916 --- /dev/null +++ b/test/e2e/conformance/tests/go-wasm-ai-cache.go @@ -0,0 +1,76 @@ +// 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(WasmPluginsAiCache) +} + +var WasmPluginsAiCache = suite.ConformanceTest{ + ShortName: "WasmPluginAiCache", + Description: "The Ingress in the higress-conformance-infra namespace test the ai-cache WASM plugin.", + Features: []suite.SupportedFeature{suite.WASMGoConformanceFeature}, + Manifests: []string{"tests/go-wasm-ai-cache.yaml"}, + Test: func(t *testing.T, suite *suite.ConformanceTestSuite) { + testcases := []http.Assertion{ + { + Meta: http.AssertionMeta{ + TestCaseName: "case 1: basic", + TargetBackend: "infra-backend-v1", + TargetNamespace: "higress-conformance-infra", + }, + Request: http.AssertionRequest{ + ActualRequest: http.Request{ + Host: "dashscope.aliyuncs.com", + Path: "/v1/chat/completions", + Method: "POST", + ContentType: http.ContentTypeApplicationJson, + Body: []byte(`{ + "model": "qwen-long", + "messages": [{"role":"user","content":"hi"}]}`), + }, + ExpectedRequest: &http.ExpectedRequest{ + Request: http.Request{ + Host: "dashscope.aliyuncs.com", + Path: "/compatible-mode/v1/chat/completions", + Method: "POST", + ContentType: http.ContentTypeApplicationJson, + Body: []byte(`{ + "model": "qwen-long", + "messages": [{"role":"user","content":"hi"}]}`), + }, + }, + }, + Response: http.AssertionResponse{ + ExpectedResponse: http.Response{ + StatusCode: 200, + }, + }, + }, + } + t.Run("WasmPlugins ai-cache", func(t *testing.T) { + for _, testcase := range testcases { + http.MakeRequestAndExpectEventuallyConsistentResponse(t, suite.RoundTripper, suite.TimeoutConfig, suite.GatewayAddress, testcase) + } + }) + }, +} diff --git a/test/e2e/conformance/tests/go-wasm-ai-cache.yaml b/test/e2e/conformance/tests/go-wasm-ai-cache.yaml new file mode 100644 index 0000000000..c7d6b0c46b --- /dev/null +++ b/test/e2e/conformance/tests/go-wasm-ai-cache.yaml @@ -0,0 +1,103 @@ +# 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-ai-cache-openai + namespace: higress-conformance-infra +spec: + ingressClassName: higress + rules: + - host: "dashscope.aliyuncs.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + annotations: + name: wasmplugin-ai-cache-qwen + namespace: higress-conformance-infra +spec: + ingressClassName: higress + rules: + - host: "qwen.ai.com" + http: + paths: + - pathType: Prefix + path: "/" + backend: + service: + name: infra-backend-v1 + port: + number: 8080 +--- +apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + name: ai-cache + namespace: higress-system +spec: + priority: 400 + matchRules: + - config: + embedding: + type: "dashscope" + serviceName: "qwen" + apiKey: "{{secret.qwenApiKey}}" + timeout: 12000 + vector: + type: "dashvector" + serviceName: "dashvector" + collectionID: "{{secret.collectionID}}" + serviceDomain: "{{secret.serviceDomain}}" + apiKey: "{{secret.apiKey}}" + timeout: 12000 + cache: + + ingress: + - higress-conformance-infra/wasmplugin-ai-cache-openai + - higress-conformance-infra/wasmplugin-ai-cache-qwen + # url: file:///opt/plugins/wasm-go/extensions/ai-cache/plugin.wasm + url: oci://registry.cn-shanghai.aliyuncs.com/suchunsv/higress_ai:1.18 +--- +apiVersion: extensions.higress.io/v1alpha1 +kind: WasmPlugin +metadata: + name: ai-proxy + namespace: higress-system +spec: + priority: 201 + matchRules: + - config: + provider: + type: "qwen" + qwenEnableCompatible: true + apiTokens: + - "{{secret.qwenApiKey}}" + timeout: 1200000 + modelMapping: + "*": "qwen-long" + ingress: + - higress-conformance-infra/wasmplugin-ai-cache-openai + - higress-conformance-infra/wasmplugin-ai-cache-qwen + url: oci://higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/ai-proxy:1.0.0 From 7c33ebf6ea5ad58ed6d42652fdf3f3a50c4b07ae Mon Sep 17 00:00:00 2001 From: tmsnan <877625878@qq.com> Date: Mon, 28 Oct 2024 16:26:29 +0800 Subject: [PATCH 33/64] bugfix: remove config check and fix memory leak in traffic-tag (#1435) --- .../wasm-go/extensions/traffic-tag/content.go | 4 ---- .../wasm-go/extensions/traffic-tag/main.go | 21 ++++++++++++++---- .../wasm-go/extensions/traffic-tag/parse.go | 3 --- .../wasm-go/extensions/traffic-tag/utils.go | 22 ------------------- .../wasm-go/extensions/traffic-tag/weight.go | 9 ++------ 5 files changed, 19 insertions(+), 40 deletions(-) diff --git a/plugins/wasm-go/extensions/traffic-tag/content.go b/plugins/wasm-go/extensions/traffic-tag/content.go index 07750f3314..4d62c02c99 100644 --- a/plugins/wasm-go/extensions/traffic-tag/content.go +++ b/plugins/wasm-go/extensions/traffic-tag/content.go @@ -26,10 +26,6 @@ import ( ) func onContentRequestHeaders(conditionGroups []ConditionGroup, log wrapper.Log) bool { - if len(conditionGroups) == 0 { - return false - } - for _, cg := range conditionGroups { if matchCondition(&cg, log) { addTagHeader(cg.HeaderName, cg.HeaderValue, log) diff --git a/plugins/wasm-go/extensions/traffic-tag/main.go b/plugins/wasm-go/extensions/traffic-tag/main.go index 6f69f23d64..0e58f95084 100644 --- a/plugins/wasm-go/extensions/traffic-tag/main.go +++ b/plugins/wasm-go/extensions/traffic-tag/main.go @@ -16,6 +16,7 @@ package main import ( "math/rand" + "strings" "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm/types" @@ -60,7 +61,6 @@ type TrafficTagConfig struct { WeightGroups []WeightGroup `json:"weightGroups,omitempty"` DefaultTagKey string `json:"defaultTagKey,omitempty"` DefaultTagVal string `json:"defaultTagVal,omitempty"` - randGen *rand.Rand } type ConditionGroup struct { @@ -93,8 +93,11 @@ func main() { } func parseConfig(json gjson.Result, config *TrafficTagConfig, log wrapper.Log) error { - if err := jsonValidate(json, log); err != nil { - return err + + jsonStr := strings.TrimSpace(json.Raw) + if jsonStr == "{}" || jsonStr == "" { + log.Error("plugin config is empty") + return nil } err := parseContentConfig(json, config, log) @@ -106,7 +109,17 @@ func parseConfig(json gjson.Result, config *TrafficTagConfig, log wrapper.Log) e } func onHttpRequestHeaders(ctx wrapper.HttpContext, config TrafficTagConfig, log wrapper.Log) types.Action { - if add := (onContentRequestHeaders(config.ConditionGroups, log) || onWeightRequestHeaders(config.WeightGroups, config.randGen, log)); !add { + + add := false + if len(config.ConditionGroups) != 0 { + add = add || onContentRequestHeaders(config.ConditionGroups, log) + } + + if !add && len(config.WeightGroups) != 0 { + add = add || onWeightRequestHeaders(config.WeightGroups, rand.Uint64(), log) + } + + if !add { setDefaultTag(config.DefaultTagKey, config.DefaultTagVal, log) } diff --git a/plugins/wasm-go/extensions/traffic-tag/parse.go b/plugins/wasm-go/extensions/traffic-tag/parse.go index 7878c3ba9b..c41890c1a7 100644 --- a/plugins/wasm-go/extensions/traffic-tag/parse.go +++ b/plugins/wasm-go/extensions/traffic-tag/parse.go @@ -17,10 +17,8 @@ package main import ( "errors" "fmt" - "math/rand" "strconv" "strings" - "time" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" regexp "github.com/wasilibs/go-re2" @@ -85,7 +83,6 @@ func parseWeightConfig(json gjson.Result, config *TrafficTagConfig, log wrapper. var parseError error var accumulatedWeight int64 config.WeightGroups = []WeightGroup{} - config.randGen = rand.New(rand.NewSource(time.Now().UnixNano())) // parse default tag key and value if k, v := json.Get(DefaultTagKey), json.Get(DefaultTagVal); k.Exists() && v.Exists() { diff --git a/plugins/wasm-go/extensions/traffic-tag/utils.go b/plugins/wasm-go/extensions/traffic-tag/utils.go index 870150dcc0..dd33efae84 100644 --- a/plugins/wasm-go/extensions/traffic-tag/utils.go +++ b/plugins/wasm-go/extensions/traffic-tag/utils.go @@ -15,14 +15,12 @@ package main import ( - "errors" "fmt" "net/url" "strings" "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" - "github.com/tidwall/gjson" ) func setDefaultTag(k string, v string, log wrapper.Log) { @@ -75,23 +73,3 @@ func addTagHeader(key string, value string, log wrapper.Log) { } log.Infof("ADD HEADER: %s, value: %s", key, value) } - -func jsonValidate(json gjson.Result, log wrapper.Log) error { - if !json.Exists() { - log.Error("plugin config is missing in JSON") - return errors.New("plugin config is missing in JSON") - } - - jsonStr := strings.TrimSpace(json.Raw) - if jsonStr == "{}" || jsonStr == "" { - log.Error("plugin config is empty") - return errors.New("plugin config is empty") - } - - if !gjson.Valid(json.Raw) { - log.Error("plugin config is invalid JSON") - return errors.New("plugin config is invalid JSON") - } - - return nil -} diff --git a/plugins/wasm-go/extensions/traffic-tag/weight.go b/plugins/wasm-go/extensions/traffic-tag/weight.go index d6ea49f5d9..f825f1ec7c 100644 --- a/plugins/wasm-go/extensions/traffic-tag/weight.go +++ b/plugins/wasm-go/extensions/traffic-tag/weight.go @@ -15,16 +15,11 @@ package main import ( - "math/rand" - "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" ) -func onWeightRequestHeaders(weightGroups []WeightGroup, randGen *rand.Rand, log wrapper.Log) bool { - if len(weightGroups) == 0 { - return false - } - randomValue := randGen.Uint64() % TotalWeight +func onWeightRequestHeaders(weightGroups []WeightGroup, randomNum uint64, log wrapper.Log) bool { + randomValue := randomNum % TotalWeight log.Debugf("random value for weighted headers : %d", randomValue) // CDF for _, wg := range weightGroups { From 0b42836e8538385df5fb54c3bfb70d3d8af08973 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Mon, 28 Oct 2024 18:56:09 +0800 Subject: [PATCH 34/64] Update CODEOWNERS --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index fab96a6ec9..3d36c596c3 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,7 @@ /istio @SpecialYang @johnlanni /pkg @SpecialYang @johnlanni @CH3CHO /plugins @johnlanni @WeixinX @CH3CHO -/plugins/wasm-rust @007gzs +/plugins/wasm-rust @007gzs @jizhuozhi /registry @NameHaibinZhang @2456868764 @johnlanni /test @Xunzhuo @2456868764 @CH3CHO /tools @johnlanni @Xunzhuo @2456868764 From b1550e91abbd51cd2d46200131f4227389ccf403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Mon, 28 Oct 2024 19:14:57 +0800 Subject: [PATCH 35/64] rel: Release v2.0.2 (#1438) --- VERSION | 2 +- helm/core/Chart.yaml | 4 ++-- helm/higress/Chart.lock | 6 +++--- helm/higress/Chart.yaml | 6 +++--- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/VERSION b/VERSION index 0ac852dded..f3b15f3f8e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.1 +v2.0.2 diff --git a/helm/core/Chart.yaml b/helm/core/Chart.yaml index 1dca35d253..04d287a95f 100644 --- a/helm/core/Chart.yaml +++ b/helm/core/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.0.1 +appVersion: 2.0.2 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.1 +version: 2.0.2 diff --git a/helm/higress/Chart.lock b/helm/higress/Chart.lock index e2397ea62a..9c71c9f1c8 100644 --- a/helm/higress/Chart.lock +++ b/helm/higress/Chart.lock @@ -1,9 +1,9 @@ dependencies: - name: higress-core repository: file://../core - version: 2.0.1 + version: 2.0.2 - name: higress-console repository: https://higress.io/helm-charts/ version: 1.4.4 -digest: sha256:6e4d77c31c834a404a728ec5a8379dd5df27a7e9b998a08e6524dc6534b07c1d -generated: "2024-10-09T20:07:21.857942+08:00" +digest: sha256:a424449caa01a71798c7fec9769ef97be7658354c028a3cede4790e4b6094532 +generated: "2024-10-28T18:50:27.528097+08:00" diff --git a/helm/higress/Chart.yaml b/helm/higress/Chart.yaml index 7d683863ba..51fe7f7de2 100644 --- a/helm/higress/Chart.yaml +++ b/helm/higress/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.0.1 +appVersion: 2.0.2 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.1 + version: 2.0.2 - name: higress-console repository: "https://higress.io/helm-charts/" version: 1.4.4 type: application -version: 2.0.1 +version: 2.0.2 From 7c2d2b285572fdc0f242ea90ad1a57047f9c2506 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 29 Oct 2024 09:01:06 +0800 Subject: [PATCH 36/64] fix destinationrule merge logic (#1439) --- pkg/ingress/config/ingress_config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/ingress/config/ingress_config.go b/pkg/ingress/config/ingress_config.go index e41181676a..9c2fc94b9b 100644 --- a/pkg/ingress/config/ingress_config.go +++ b/pkg/ingress/config/ingress_config.go @@ -719,9 +719,9 @@ func (m *IngressConfig) convertDestinationRule(configs []common.WrapperConfig) [ } else if dr.DestinationRule.TrafficPolicy != nil { portTrafficPolicy := destinationRuleWrapper.DestinationRule.TrafficPolicy.PortLevelSettings[0] portUpdated := false - for _, portTrafficPolicy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings { - if portTrafficPolicy.Port.Number == portTrafficPolicy.Port.Number { - portTrafficPolicy.Tls = portTrafficPolicy.Tls + for _, policy := range dr.DestinationRule.TrafficPolicy.PortLevelSettings { + if policy.Port.Number == portTrafficPolicy.Port.Number { + policy.Tls = portTrafficPolicy.Tls portUpdated = true break } From 93c1e5c2bbbb7cf632b118cc1f0526490695990a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=AA=E5=8D=93=E5=BF=97?= Date: Tue, 29 Oct 2024 17:06:03 +0800 Subject: [PATCH 37/64] feat: lazy formatted log (#1441) --- plugins/wasm-rust/src/log.rs | 93 ++++++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/plugins/wasm-rust/src/log.rs b/plugins/wasm-rust/src/log.rs index 4656b8669b..e469558673 100644 --- a/plugins/wasm-rust/src/log.rs +++ b/plugins/wasm-rust/src/log.rs @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use proxy_wasm::hostcalls; +use proxy_wasm::{hostcalls, types}; +use std::fmt::Arguments; pub enum LogLevel { Trace, @@ -34,14 +35,7 @@ impl Log { fn log(&self, level: LogLevel, msg: &str) { let msg = format!("[{}] {}", self.plugin_name, msg); - let level = match level { - LogLevel::Trace => proxy_wasm::types::LogLevel::Trace, - LogLevel::Debug => proxy_wasm::types::LogLevel::Debug, - LogLevel::Info => proxy_wasm::types::LogLevel::Info, - LogLevel::Warn => proxy_wasm::types::LogLevel::Warn, - LogLevel::Error => proxy_wasm::types::LogLevel::Error, - LogLevel::Critical => proxy_wasm::types::LogLevel::Critical, - }; + let level = types::LogLevel::from(level); hostcalls::log(level, msg.as_str()).unwrap(); } @@ -68,4 +62,85 @@ impl Log { pub fn critical(&self, msg: &str) { self.log(LogLevel::Critical, msg) } + + fn logf(&self, level: LogLevel, format_args: Arguments) { + let level = types::LogLevel::from(level); + if let Ok(log_level) = hostcalls::get_log_level() { + if (level as i32) < (log_level as i32) { + return; + } + hostcalls::log( + level, + format!("[{}] {}", self.plugin_name, format_args).as_str(), + ) + .unwrap(); + } + } + + /// ``` + /// use higress_wasm_rust::log::Log; + /// let log = Log::new("foobar".into_string()); + /// log.tracef(format_args!("Hello, {}!","World")); + /// ``` + pub fn tracef(&self, format_args: Arguments) { + self.logf(LogLevel::Trace, format_args) + } + + /// ``` + /// use higress_wasm_rust::log::Log; + /// let log = Log::new("foobar".into_string()); + /// log.debugf(format_args!("Hello, {}!","World")); + /// ``` + pub fn debugf(&self, format_args: Arguments) { + self.logf(LogLevel::Debug, format_args) + } + + /// ``` + /// use higress_wasm_rust::log::Log; + /// let log = Log::new("foobar".into_string()); + /// log.infof(format_args!("Hello, {}!","World")); + /// ``` + pub fn infof(&self, format_args: Arguments) { + self.logf(LogLevel::Info, format_args) + } + + /// ``` + /// use higress_wasm_rust::log::Log; + /// let log = Log::new("foobar".into_string()); + /// log.warnf(format_args!("Hello, {}!","World")); + /// ``` + pub fn warnf(&self, format_args: Arguments) { + self.logf(LogLevel::Warn, format_args) + } + + /// ``` + /// use higress_wasm_rust::log::Log; + /// let log = Log::new("foobar".into_string()); + /// log.errorf(format_args!("Hello, {}!","World")); + /// ``` + pub fn errorf(&self, format_args: Arguments) { + self.logf(LogLevel::Error, format_args) + } + + /// ``` + /// use higress_wasm_rust::log::Log; + /// let log = Log::new("foobar".into_string()); + /// log.criticalf(format_args!("Hello, {}!","World")); + /// ``` + pub fn criticalf(&self, format_args: Arguments) { + self.logf(LogLevel::Critical, format_args) + } +} + +impl From for types::LogLevel { + fn from(value: LogLevel) -> Self { + match value { + LogLevel::Trace => types::LogLevel::Trace, + LogLevel::Debug => types::LogLevel::Debug, + LogLevel::Info => types::LogLevel::Info, + LogLevel::Warn => types::LogLevel::Warn, + LogLevel::Error => types::LogLevel::Error, + LogLevel::Critical => types::LogLevel::Critical, + } + } } From 2219a1789814a0cdbfd879861f6d91268b60ace2 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Tue, 29 Oct 2024 19:35:02 +0800 Subject: [PATCH 38/64] [feat] Support redis call with wasm-rust (#1417) --- plugins/wasm-rust/Cargo.lock | 158 +++- plugins/wasm-rust/Cargo.toml | 1 + .../wasm-rust/example/sse-timing/src/lib.rs | 4 +- .../extensions/ai-data-masking/Cargo.lock | 162 +++- .../extensions/ai-data-masking/src/lib.rs | 32 +- .../wasm-rust/extensions/demo-wasm/Cargo.lock | 160 +++- .../wasm-rust/extensions/demo-wasm/Cargo.toml | 4 +- .../wasm-rust/extensions/demo-wasm/src/lib.rs | 128 +++- .../extensions/request-block/Cargo.lock | 158 +++- .../extensions/request-block/src/lib.rs | 8 +- .../wasm-rust/extensions/say-hello/Cargo.lock | 158 +++- plugins/wasm-rust/src/cluster_wrapper.rs | 9 + plugins/wasm-rust/src/event_stream.rs | 49 +- plugins/wasm-rust/src/internal.rs | 23 +- plugins/wasm-rust/src/lib.rs | 1 + plugins/wasm-rust/src/plugin_wrapper.rs | 63 +- plugins/wasm-rust/src/redis_wrapper.rs | 724 ++++++++++++++++++ plugins/wasm-rust/src/request_wrapper.rs | 2 + 18 files changed, 1694 insertions(+), 150 deletions(-) create mode 100644 plugins/wasm-rust/src/redis_wrapper.rs diff --git a/plugins/wasm-rust/Cargo.lock b/plugins/wasm-rust/Cargo.lock index 9cf5140c77..63272f9a61 100644 --- a/plugins/wasm-rust/Cargo.lock +++ b/plugins/wasm-rust/Cargo.lock @@ -20,11 +20,23 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" @@ -32,6 +44,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -44,6 +66,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -74,6 +105,7 @@ dependencies = [ "lazy_static", "multimap", "proxy-wasm", + "redis", "serde", "serde_json", "uuid", @@ -90,6 +122,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "1.0.11" @@ -104,9 +146,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "log" @@ -129,12 +171,46 @@ dependencies = [ "serde", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "proc-macro2" version = "1.0.88" @@ -147,7 +223,7 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" dependencies = [ "downcast-rs", "hashbrown", @@ -163,6 +239,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redis" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" +dependencies = [ + "arc-swap", + "combine", + "itoa", + "num-bigint", + "percent-encoding", + "ryu", + "url", +] + [[package]] name = "ryu" version = "1.0.18" @@ -171,18 +262,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" dependencies = [ "proc-macro2", "quote", @@ -191,9 +282,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -203,21 +294,62 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.11.0" diff --git a/plugins/wasm-rust/Cargo.toml b/plugins/wasm-rust/Cargo.toml index e4fc274311..f58f2c8069 100644 --- a/plugins/wasm-rust/Cargo.toml +++ b/plugins/wasm-rust/Cargo.toml @@ -14,3 +14,4 @@ multimap = "0" http = "1" lazy_static = "1" downcast-rs="1" +redis={version = "0", default-features = false} diff --git a/plugins/wasm-rust/example/sse-timing/src/lib.rs b/plugins/wasm-rust/example/sse-timing/src/lib.rs index ab1fa4f6c4..fbff5daf9e 100644 --- a/plugins/wasm-rust/example/sse-timing/src/lib.rs +++ b/plugins/wasm-rust/example/sse-timing/src/lib.rs @@ -60,10 +60,10 @@ impl SseTimingRoot { impl Context for SseTimingRoot {} impl RootContext for SseTimingRoot { - fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool { + fn on_configure(&mut self, plugin_configuration_size: usize) -> bool { on_configure( self, - _plugin_configuration_size, + plugin_configuration_size, self.rule_matcher.borrow_mut().deref_mut(), &self.log, ) diff --git a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock b/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock index 914d5b9689..e635ae454f 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock +++ b/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock @@ -46,6 +46,18 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bit-set" version = "0.5.3" @@ -84,15 +96,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cc" -version = "1.1.30" +version = "1.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" dependencies = [ "shlex", ] @@ -112,6 +124,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "cpufeatures" version = "0.2.14" @@ -230,6 +252,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fxhash" version = "0.2.1" @@ -295,6 +326,7 @@ dependencies = [ "lazy_static", "multimap", "proxy-wasm", + "redis", "serde", "serde_json", "uuid", @@ -317,6 +349,16 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "1.0.11" @@ -359,9 +401,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "log" @@ -390,6 +432,34 @@ dependencies = [ "serde", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.20.2" @@ -418,6 +488,12 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.7.14" @@ -519,7 +595,7 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" dependencies = [ "downcast-rs", "hashbrown", @@ -550,6 +626,21 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +[[package]] +name = "redis" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" +dependencies = [ + "arc-swap", + "combine", + "itoa", + "num-bigint", + "percent-encoding", + "ryu", + "url", +] + [[package]] name = "regex" version = "1.11.0" @@ -630,18 +721,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" dependencies = [ "proc-macro2", "quote", @@ -650,9 +741,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -697,9 +788,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", @@ -726,6 +817,21 @@ dependencies = [ "syn", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "typenum" version = "1.17.0" @@ -738,12 +844,38 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.11.0" diff --git a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs index 89c0da5219..2f2999d673 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs +++ b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs @@ -97,13 +97,13 @@ where D: Deserializer<'de>, { let value: Value = Deserialize::deserialize(deserializer)?; - if let Some(_type) = value.as_str() { - if _type == "replace" { + if let Some(t) = value.as_str() { + if t == "replace" { Ok(Type::Replace) - } else if _type == "hash" { + } else if t == "hash" { Ok(Type::Hash) } else { - Err(Error::custom(format!("regexp error value {}", _type))) + Err(Error::custom(format!("regexp error value {}", t))) } } else { Err(Error::custom("type error not string".to_string())) @@ -238,8 +238,8 @@ impl DenyWord { let mut deny_word = DenyWord::empty(); for word in words { - let _w = word.into(); - let w = _w.trim(); + let word_s = word.into(); + let w = word_s.trim(); if w.is_empty() { continue; } @@ -314,12 +314,12 @@ impl System { fn grok_to_pattern(&self, pattern: &str) -> (String, bool) { let mut ok = true; let mut ret = pattern.to_string(); - for _c in self.grok_regex.captures_iter(pattern) { - if _c.is_err() { + for capture in self.grok_regex.captures_iter(pattern) { + if capture.is_err() { ok = false; continue; } - let c = _c.unwrap(); + let c = capture.unwrap(); if let (Some(full), Some(name)) = (c.get(0), c.name("pattern")) { if let Some(p) = self.grok_patterns.get(name.as_str()) { if let Some(alias) = c.name("alias") { @@ -347,16 +347,16 @@ impl AiDataMaskingRoot { impl Context for AiDataMaskingRoot {} impl RootContext for AiDataMaskingRoot { - fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool { + fn on_configure(&mut self, plugin_configuration_size: usize) -> bool { on_configure( self, - _plugin_configuration_size, + plugin_configuration_size, self.rule_matcher.borrow_mut().deref_mut(), &self.log, ) } - fn create_http_context(&self, _context_id: u32) -> Option> { - self.create_http_context_use_wrapper(_context_id) + fn create_http_context(&self, context_id: u32) -> Option> { + self.create_http_context_use_wrapper(context_id) } fn get_type(&self) -> Option { Some(ContextType::HttpContext) @@ -491,11 +491,11 @@ impl AiDataMasking { if rule.type_ == Type::Replace && !rule.restore { msg = rule.regex.replace_all(&msg, &rule.value).to_string(); } else { - for _m in rule.regex.find_iter(&msg) { - if _m.is_err() { + for mc in rule.regex.find_iter(&msg) { + if mc.is_err() { continue; } - let m = _m.unwrap(); + let m = mc.unwrap(); let from_word = m.as_str(); let to_word = match rule.type_ { diff --git a/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock b/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock index c1e197caf6..1c6c135617 100644 --- a/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock +++ b/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock @@ -20,11 +20,23 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" @@ -32,6 +44,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "demo-wasm" version = "0.1.0" @@ -40,7 +62,9 @@ dependencies = [ "http", "multimap", "proxy-wasm", + "redis", "serde", + "serde_json", ] [[package]] @@ -55,6 +79,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -85,6 +118,7 @@ dependencies = [ "lazy_static", "multimap", "proxy-wasm", + "redis", "serde", "serde_json", "uuid", @@ -101,6 +135,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "1.0.11" @@ -115,9 +159,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "log" @@ -140,12 +184,46 @@ dependencies = [ "serde", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "proc-macro2" version = "1.0.88" @@ -158,7 +236,7 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" dependencies = [ "downcast-rs", "hashbrown", @@ -174,6 +252,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redis" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" +dependencies = [ + "arc-swap", + "combine", + "itoa", + "num-bigint", + "percent-encoding", + "ryu", + "url", +] + [[package]] name = "ryu" version = "1.0.18" @@ -182,18 +275,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" dependencies = [ "proc-macro2", "quote", @@ -202,9 +295,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -214,21 +307,62 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.11.0" diff --git a/plugins/wasm-rust/extensions/demo-wasm/Cargo.toml b/plugins/wasm-rust/extensions/demo-wasm/Cargo.toml index a517c2b531..3f1e3fd864 100644 --- a/plugins/wasm-rust/extensions/demo-wasm/Cargo.toml +++ b/plugins/wasm-rust/extensions/demo-wasm/Cargo.toml @@ -11,5 +11,7 @@ crate-type = ["cdylib"] higress-wasm-rust = { path = "../../", version = "0.1.0" } proxy-wasm = { git="https://github.com/higress-group/proxy-wasm-rust-sdk", branch="main", version="0.2.2" } serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" multimap = "*" -http = "*" \ No newline at end of file +http = "*" +redis={version = "0", default-features = false} \ No newline at end of file diff --git a/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs b/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs index 62dfa055d0..ed432e69ad 100644 --- a/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs +++ b/plugins/wasm-rust/extensions/demo-wasm/src/lib.rs @@ -1,13 +1,16 @@ -use higress_wasm_rust::cluster_wrapper::DnsCluster; +use higress_wasm_rust::cluster_wrapper::{DnsCluster, StaticIpCluster}; use higress_wasm_rust::log::Log; use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper}; +use higress_wasm_rust::redis_wrapper::{RedisClient, RedisClientBuilder, RedisClientConfig}; use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher}; use http::Method; use multimap::MultiMap; use proxy_wasm::traits::{Context, HttpContext, RootContext}; use proxy_wasm::types::{Bytes, ContextType, DataAction, HeaderAction, LogLevel}; +use redis::Value; use serde::Deserialize; +use serde_json::json; use std::cell::RefCell; use std::ops::DerefMut; use std::rc::{Rc, Weak}; @@ -24,6 +27,8 @@ const PLUGIN_NAME: &str = "demo-wasm"; struct DemoWasmConfig { // 配置文件结构体 test: String, + #[serde(default)] + password: Option, } fn format_body(body: Option>) -> String { @@ -40,6 +45,8 @@ struct DemoWasm { log: Log, config: Option>, weak: Weak>>>, + redis_client: Option, + cid: i64, } impl Context for DemoWasm {} @@ -58,7 +65,7 @@ impl HttpContextWrapper for DemoWasm { fn on_config(&mut self, config: Rc) { // 获取config self.log.info(&format!("on_config {}", config.test)); - self.config = Some(config.clone()) + self.config = Some(config.clone()); } fn on_http_request_complete_headers( &mut self, @@ -67,6 +74,55 @@ impl HttpContextWrapper for DemoWasm { // 请求header获取完成回调 self.log .info(&format!("on_http_request_complete_headers {:?}", headers)); + if let Some(config) = &self.config { + let _redis_client = RedisClientBuilder::new( + &StaticIpCluster::new("redis", 80, ""), + Duration::from_secs(5), + ) + .password(config.password.as_ref()) + .build(); + + let redis_client = RedisClient::new( + RedisClientConfig::new( + &StaticIpCluster::new("redis", 80, ""), + Duration::from_secs(5), + ) + .password(config.password.as_ref()), + ); + + if let Some(self_rc) = self.weak.upgrade() { + let init_res = redis_client.init(); + self.log.info(&format!("redis init {:?}", init_res)); + if init_res.is_ok() { + let incr_res = redis_client.incr( + "connect", + Box::new(move |res, status, token_id| { + self_rc.borrow().log().info(&format!( + "redis incr finish value_res:{:?}, status: {}, token_id: {}", + res, status, token_id + )); + if let Some(this) = self_rc.borrow_mut().downcast_mut::() { + if let Ok(Value::Int(value)) = res { + this.cid = *value; + } + } + self_rc.borrow().resume_http_request(); + }), + ); + match incr_res { + Ok(s) => { + self.log.info(&format!("redis incr ok {}", s)); + return HeaderAction::StopAllIterationAndBuffer; + } + Err(e) => self.log.info(&format!("redis incr error {:?}", e)), + }; + } + self.redis_client = Some(redis_client); + } else { + self.log.error("self_weak upgrade error"); + } + } + HeaderAction::Continue } fn on_http_response_complete_headers( @@ -76,6 +132,38 @@ impl HttpContextWrapper for DemoWasm { // 返回header获取完成回调 self.log .info(&format!("on_http_response_complete_headers {:?}", headers)); + self.set_http_response_header("Content-Length", None); + let self_rc = match self.weak.upgrade() { + Some(rc) => rc.clone(), + None => { + self.log.error("self_weak upgrade error"); + return HeaderAction::Continue; + } + }; + if let Some(redis_client) = &self.redis_client { + match redis_client.get( + "connect", + Box::new(move |res, status, token_id| { + if let Some(this) = self_rc.borrow().downcast_ref::() { + this.log.info(&format!( + "redis get connect value_res:{:?}, status: {}, token_id: {}", + res, status, token_id + )); + this.resume_http_response(); + } else { + self_rc.borrow().resume_http_response(); + } + }), + ) { + Ok(o) => { + self.log.info(&format!("redis get ok {}", o)); + return HeaderAction::StopIteration; + } + Err(e) => { + self.log.info(&format!("redis get fail {:?}", e)); + } + } + } HeaderAction::Continue } fn cache_request_body(&self) -> bool { @@ -92,6 +180,16 @@ impl HttpContextWrapper for DemoWasm { "on_http_request_complete_body {}", String::from_utf8(req_body.clone()).unwrap_or("".to_string()) )); + DataAction::Continue + } + fn on_http_response_complete_body(&mut self, res_body: &Bytes) -> DataAction { + // 返回body获取完成回调 + let res_body_string = String::from_utf8(res_body.clone()).unwrap_or("".to_string()); + self.log.info(&format!( + "on_http_response_complete_body {}", + res_body_string + )); + let cluster = DnsCluster::new("httpbin", "httpbin.org", 80); let self_rc = match self.weak.upgrade() { @@ -101,6 +199,7 @@ impl HttpContextWrapper for DemoWasm { return DataAction::Continue; } }; + let http_call_res = self.http_call( &cluster, &Method::POST, @@ -108,34 +207,29 @@ impl HttpContextWrapper for DemoWasm { MultiMap::new(), Some("test_body".as_bytes()), Box::new(move |status_code, headers, body| { - if let Some(this) = self_rc.borrow().downcast_ref::() { + if let Some(this) = self_rc.borrow_mut().downcast_mut::() { + let body_string = format_body(body); this.log.info(&format!( "test_callback status_code:{}, headers: {:?}, body: {}", status_code, headers, - format_body(body) + body_string )); - this.resume_http_request(); + let data = json!({"redis_cid": this.cid, "http_call_body": body_string, "res_body": res_body_string}); + this.replace_http_response_body(data.to_string().as_bytes()); + this.resume_http_response(); } else { - self_rc.borrow().resume_http_request(); + self_rc.borrow().resume_http_response(); } }), Duration::from_secs(5), ); match http_call_res { - Ok(_) => DataAction::StopIterationAndBuffer, + Ok(_) => return DataAction::StopIterationAndBuffer, Err(e) => { self.log.info(&format!("http_call fail {:?}", e)); - DataAction::Continue } } - } - fn on_http_response_complete_body(&mut self, res_body: &Bytes) -> DataAction { - // 返回body获取完成回调 - self.log.info(&format!( - "on_http_response_complete_body {}", - String::from_utf8(res_body.clone()).unwrap_or("".to_string()) - )); DataAction::Continue } } @@ -147,6 +241,7 @@ impl DemoWasmRoot { fn new() -> Self { let log = Log::new(PLUGIN_NAME.to_string()); log.info("DemoWasmRoot::new"); + DemoWasmRoot { log, rule_matcher: Rc::new(RefCell::new(RuleMatcher::default())), @@ -171,6 +266,7 @@ impl RootContext for DemoWasmRoot { "DemoWasmRoot::create_http_context({})", context_id )); + self.create_http_context_use_wrapper(context_id) } fn get_type(&self) -> Option { @@ -191,6 +287,8 @@ impl RootContextWrapper for DemoWasmRoot { config: None, log: Log::new(PLUGIN_NAME.to_string()), weak: Weak::default(), + redis_client: None, + cid: -1, })) } } diff --git a/plugins/wasm-rust/extensions/request-block/Cargo.lock b/plugins/wasm-rust/extensions/request-block/Cargo.lock index 243fe87a49..36c0b812b7 100644 --- a/plugins/wasm-rust/extensions/request-block/Cargo.lock +++ b/plugins/wasm-rust/extensions/request-block/Cargo.lock @@ -29,11 +29,23 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" @@ -41,6 +53,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -53,6 +75,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -83,6 +114,7 @@ dependencies = [ "lazy_static", "multimap", "proxy-wasm", + "redis", "serde", "serde_json", "uuid", @@ -99,6 +131,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "1.0.11" @@ -113,9 +155,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "log" @@ -138,12 +180,46 @@ dependencies = [ "serde", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "proc-macro2" version = "1.0.88" @@ -156,7 +232,7 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" dependencies = [ "downcast-rs", "hashbrown", @@ -172,6 +248,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redis" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" +dependencies = [ + "arc-swap", + "combine", + "itoa", + "num-bigint", + "percent-encoding", + "ryu", + "url", +] + [[package]] name = "regex" version = "1.11.0" @@ -221,18 +312,18 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "serde" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" dependencies = [ "proc-macro2", "quote", @@ -241,9 +332,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -253,21 +344,62 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.11.0" diff --git a/plugins/wasm-rust/extensions/request-block/src/lib.rs b/plugins/wasm-rust/extensions/request-block/src/lib.rs index d120acb902..822601cd76 100644 --- a/plugins/wasm-rust/extensions/request-block/src/lib.rs +++ b/plugins/wasm-rust/extensions/request-block/src/lib.rs @@ -104,17 +104,17 @@ impl RquestBlockRoot { impl Context for RquestBlockRoot {} impl RootContext for RquestBlockRoot { - fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool { + fn on_configure(&mut self, plugin_configuration_size: usize) -> bool { let ret = on_configure( self, - _plugin_configuration_size, + plugin_configuration_size, self.rule_matcher.borrow_mut().deref_mut(), &self.log, ); ret } - fn create_http_context(&self, _context_id: u32) -> Option> { - self.create_http_context_use_wrapper(_context_id) + fn create_http_context(&self, context_id: u32) -> Option> { + self.create_http_context_use_wrapper(context_id) } fn get_type(&self) -> Option { Some(ContextType::HttpContext) diff --git a/plugins/wasm-rust/extensions/say-hello/Cargo.lock b/plugins/wasm-rust/extensions/say-hello/Cargo.lock index dc98fd709a..758fc617e3 100644 --- a/plugins/wasm-rust/extensions/say-hello/Cargo.lock +++ b/plugins/wasm-rust/extensions/say-hello/Cargo.lock @@ -20,11 +20,23 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "bytes" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" [[package]] name = "cfg-if" @@ -32,6 +44,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + [[package]] name = "downcast-rs" version = "1.2.1" @@ -44,6 +66,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "getrandom" version = "0.2.15" @@ -74,6 +105,7 @@ dependencies = [ "lazy_static", "multimap", "proxy-wasm", + "redis", "serde", "serde_json", "uuid", @@ -90,6 +122,16 @@ dependencies = [ "itoa", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "itoa" version = "1.0.11" @@ -104,9 +146,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.159" +version = "0.2.161" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" [[package]] name = "log" @@ -129,12 +171,46 @@ dependencies = [ "serde", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "proc-macro2" version = "1.0.88" @@ -147,7 +223,7 @@ dependencies = [ [[package]] name = "proxy-wasm" version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" +source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" dependencies = [ "downcast-rs", "hashbrown", @@ -163,6 +239,21 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redis" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" +dependencies = [ + "arc-swap", + "combine", + "itoa", + "num-bigint", + "percent-encoding", + "ryu", + "url", +] + [[package]] name = "ryu" version = "1.0.18" @@ -181,18 +272,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.211" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" dependencies = [ "proc-macro2", "quote", @@ -201,9 +292,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.128" +version = "1.0.132" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" dependencies = [ "itoa", "memchr", @@ -213,21 +304,62 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + [[package]] name = "uuid" version = "1.11.0" diff --git a/plugins/wasm-rust/src/cluster_wrapper.rs b/plugins/wasm-rust/src/cluster_wrapper.rs index 2891293fb3..8d3e204458 100644 --- a/plugins/wasm-rust/src/cluster_wrapper.rs +++ b/plugins/wasm-rust/src/cluster_wrapper.rs @@ -4,10 +4,12 @@ pub trait Cluster { fn cluster_name(&self) -> String; fn host_name(&self) -> String; } + #[derive(Debug, Clone)] pub struct RouteCluster { host: String, } + impl RouteCluster { pub fn new(host: &str) -> Self { RouteCluster { @@ -15,6 +17,7 @@ impl RouteCluster { } } } + impl Cluster for RouteCluster { fn cluster_name(&self) -> String { if let Some(res) = get_property(vec!["cluster_name"]) { @@ -111,6 +114,7 @@ impl NacosCluster { } } } + impl Cluster for NacosCluster { fn cluster_name(&self) -> String { let group = if self.group.is_empty() { @@ -154,6 +158,7 @@ impl StaticIpCluster { } } } + impl Cluster for StaticIpCluster { fn cluster_name(&self) -> String { format!("outbound|{}||{}.static", self.port, self.service_name) @@ -184,6 +189,7 @@ impl DnsCluster { } } } + impl Cluster for DnsCluster { fn cluster_name(&self) -> String { format!("outbound|{}||{}.dns", self.port, self.service_name) @@ -212,6 +218,7 @@ impl ConsulCluster { } } } + impl Cluster for ConsulCluster { fn cluster_name(&self) -> String { format!( @@ -245,10 +252,12 @@ impl FQDNCluster { } } } + impl Cluster for FQDNCluster { fn cluster_name(&self) -> String { format!("outbound|{}||{}", self.port, self.fqdn) } + fn host_name(&self) -> String { if self.host.is_empty() { self.fqdn.clone() diff --git a/plugins/wasm-rust/src/event_stream.rs b/plugins/wasm-rust/src/event_stream.rs index f28846317a..97715dcac1 100644 --- a/plugins/wasm-rust/src/event_stream.rs +++ b/plugins/wasm-rust/src/event_stream.rs @@ -28,29 +28,15 @@ /// However, in the rules of event and field, there is an ambiguous grammar in the judgment of eol, /// and it will bring ambiguity (whether the field ends). In order to eliminate this ambiguity, /// we believe that CRLF as CR+LF belongs to event and field respectively. + +#[derive(Default)] pub struct EventStream { buffer: Vec, processed_offset: usize, } -impl EventStream { - pub fn new() -> Self { - EventStream { - buffer: Vec::new(), - processed_offset: 0, - } - } - - /// Update the event stream by adding new data to the buffer and resetting processed offset if needed. - pub fn update(&mut self, data: Vec) { - if self.processed_offset > 0 { - self.buffer.drain(0..self.processed_offset); - self.processed_offset = 0; - } - - self.buffer.extend(data); - } - +impl Iterator for EventStream { + type Item = Vec; /// Get the next event from the event stream. Return the event data if available, otherwise return None. /// Next will consume all the data in the current buffer. However, if there is a valid event at the end of the buffer, /// it will return the event directly even if the data after the next `update` could be considered part of the same event @@ -71,7 +57,8 @@ impl EventStream { /// } /// } /// ``` - pub fn next(&mut self) -> Option> { + /// + fn next(&mut self) -> Option { let mut i = self.processed_offset; while i < self.buffer.len() { @@ -86,6 +73,18 @@ impl EventStream { None } +} + +impl EventStream { + /// Update the event stream by adding new data to the buffer and resetting processed offset if needed. + pub fn update(&mut self, data: Vec) { + if self.processed_offset > 0 { + self.buffer.drain(0..self.processed_offset); + self.processed_offset = 0; + } + + self.buffer.extend(data); + } /// Flush the event stream and return any remaining unprocessed event data. Return None if there is none. pub fn flush(&mut self) -> Option> { @@ -138,7 +137,7 @@ mod tests { #[test] fn test_crlf_events() { - let mut parser = EventStream::new(); + let mut parser = EventStream::default(); parser.update(b"event1\n\nevent2\n\n".to_vec()); assert_eq!(parser.next(), Some(b"event1".to_vec())); @@ -147,7 +146,7 @@ mod tests { #[test] fn test_lf_events() { - let mut parser = EventStream::new(); + let mut parser = EventStream::default(); parser.update(b"event3\n\r\nevent4\r\n".to_vec()); assert_eq!(parser.next(), Some(b"event3".to_vec())); @@ -156,7 +155,7 @@ mod tests { #[test] fn test_partial_event() { - let mut parser = EventStream::new(); + let mut parser = EventStream::default(); parser.update(b"partial_event1".to_vec()); assert_eq!(parser.next(), None); @@ -167,7 +166,7 @@ mod tests { #[test] fn test_mixed_eol_events() { - let mut parser = EventStream::new(); + let mut parser = EventStream::default(); parser.update(b"event5\r\nevent6\r\n\r\nevent7\r\n".to_vec()); assert_eq!(parser.next(), Some(b"event5".to_vec())); @@ -177,7 +176,7 @@ mod tests { #[test] fn test_mixed2_eol_events() { - let mut parser = EventStream::new(); + let mut parser = EventStream::default(); parser.update(b"event5\r\nevent6\r\n".to_vec()); assert_eq!(parser.next(), Some(b"event5".to_vec())); assert_eq!(parser.next(), Some(b"event6".to_vec())); @@ -188,7 +187,7 @@ mod tests { #[test] fn test_no_event() { - let mut parser = EventStream::new(); + let mut parser = EventStream::default(); parser.update(b"no_eol_in_this_string".to_vec()); assert_eq!(parser.next(), None); diff --git a/plugins/wasm-rust/src/internal.rs b/plugins/wasm-rust/src/internal.rs index 4116c5d798..5a562419d4 100644 --- a/plugins/wasm-rust/src/internal.rs +++ b/plugins/wasm-rust/src/internal.rs @@ -14,7 +14,7 @@ #![allow(dead_code)] -use proxy_wasm::hostcalls; +use proxy_wasm::hostcalls::{self, RedisCallbackFn}; use proxy_wasm::types::{BufferType, Bytes, MapType, Status}; use std::time::{Duration, SystemTime}; @@ -381,3 +381,24 @@ pub(crate) fn send_http_response( ) { hostcalls::send_http_response(status_code, headers, body).unwrap() } + +pub(crate) fn redis_init( + upstream: &str, + username: Option<&[u8]>, + password: Option<&[u8]>, + timeout: Duration, +) -> Result<(), Status> { + hostcalls::redis_init(upstream, username, password, timeout) +} + +pub(crate) fn dispatch_redis_call( + upstream: &str, + query: &[u8], + call_fn: Box, +) -> Result { + hostcalls::dispatch_redis_call(upstream, query, call_fn) +} + +pub(crate) fn get_redis_call_response(start: usize, max_size: usize) -> Option { + hostcalls::get_buffer(BufferType::RedisCallResponse, start, max_size).unwrap() +} diff --git a/plugins/wasm-rust/src/lib.rs b/plugins/wasm-rust/src/lib.rs index f6a92be89b..2e6993e040 100644 --- a/plugins/wasm-rust/src/lib.rs +++ b/plugins/wasm-rust/src/lib.rs @@ -18,5 +18,6 @@ pub mod event_stream; mod internal; pub mod log; pub mod plugin_wrapper; +pub mod redis_wrapper; pub mod request_wrapper; pub mod rule_matcher; diff --git a/plugins/wasm-rust/src/plugin_wrapper.rs b/plugins/wasm-rust/src/plugin_wrapper.rs index abe188a99f..832cdbce5c 100644 --- a/plugins/wasm-rust/src/plugin_wrapper.rs +++ b/plugins/wasm-rust/src/plugin_wrapper.rs @@ -30,6 +30,7 @@ use serde::de::DeserializeOwned; lazy_static! { static ref LOG: Log = Log::new("plugin_wrapper".to_string()); } + thread_local! { static HTTP_CALLBACK_DISPATCHER: HttpCallbackDispatcher = HttpCallbackDispatcher::new(); } @@ -49,7 +50,9 @@ where None => None, } } + fn rule_matcher(&self) -> &SharedRuleMatcher; + fn create_http_context_wrapper( &self, _context_id: u32, @@ -63,20 +66,24 @@ pub type HttpCallbackFn = dyn FnOnce(u16, &MultiMap, Option>>, } + impl Default for HttpCallbackDispatcher { fn default() -> Self { Self::new() } } + impl HttpCallbackDispatcher { pub fn new() -> Self { HttpCallbackDispatcher { call_fns: RefCell::new(HashMap::new()), } } + pub fn set(&self, token_id: u32, arg: Box) { self.call_fns.borrow_mut().insert(token_id, arg); } + pub fn pop(&self, token_id: u32) -> Option> { self.call_fns.borrow_mut().remove(&token_id) } @@ -91,31 +98,39 @@ where _self_weak: Weak>>>, ) { } + fn log(&self) -> &Log { &LOG } + fn on_config(&mut self, _config: Rc) {} + fn on_http_request_complete_headers( &mut self, _headers: &MultiMap, ) -> HeaderAction { HeaderAction::Continue } + fn on_http_response_complete_headers( &mut self, _headers: &MultiMap, ) -> HeaderAction { HeaderAction::Continue } + fn cache_request_body(&self) -> bool { false } + fn cache_response_body(&self) -> bool { false } + fn on_http_request_complete_body(&mut self, _req_body: &Bytes) -> DataAction { DataAction::Continue } + fn on_http_response_complete_body(&mut self, _res_body: &Bytes) -> DataAction { DataAction::Continue } @@ -123,6 +138,7 @@ where fn replace_http_request_body(&mut self, body: &[u8]) { self.set_http_request_body(0, i32::MAX as usize, body) } + fn replace_http_response_body(&mut self, body: &[u8]) { self.set_http_response_body(0, i32::MAX as usize, body) } @@ -164,8 +180,8 @@ where if let Ok(token_id) = ret { HTTP_CALLBACK_DISPATCHER.with(|dispatcher| dispatcher.set(token_id, call_fn)); - self.log().debug( - &format!( + self.log().debugf( + format_args!( "http call start, id: {}, cluster: {}, method: {}, url: {}, body: {:?}, timeout: {:?}", token_id, cluster.cluster_name(), method.as_str(), raw_url, body, timeout ) @@ -173,7 +189,8 @@ where } ret } else { - self.log().critical(&format!("invalid raw_url:{}", raw_url)); + self.log() + .criticalf(format_args!("invalid raw_url:{}", raw_url)); Err(Status::ParseFailure) } } @@ -182,14 +199,13 @@ where downcast_rs::impl_downcast!(HttpContextWrapper where PluginConfig: Default + DeserializeOwned + Clone); pub struct PluginHttpWrapper { - req_headers: MultiMap, - res_headers: MultiMap, req_body_len: usize, res_body_len: usize, config: Option>, rule_matcher: SharedRuleMatcher, http_content: Rc>>>, } + impl PluginHttpWrapper where PluginConfig: Default + DeserializeOwned + Clone + 'static, @@ -203,8 +219,6 @@ where .borrow_mut() .init_self_weak(Rc::downgrade(&rc_content)); PluginHttpWrapper { - req_headers: MultiMap::new(), - res_headers: MultiMap::new(), req_body_len: 0, res_body_len: 0, config: None, @@ -212,10 +226,12 @@ where http_content: rc_content, } } + fn get_http_call_fn(&mut self, token_id: u32) -> Option> { HTTP_CALLBACK_DISPATCHER.with(|dispatcher| dispatcher.pop(token_id)) } } + impl Context for PluginHttpWrapper where PluginConfig: Default + DeserializeOwned + Clone + 'static, @@ -240,24 +256,24 @@ where status_code = code; normal_response = true; } else { - self.http_content - .borrow() - .log() - .error(&format!("failed to parse status: {}", header_value)); + self.http_content.borrow().log().errorf(format_args!( + "failed to parse status: {}", + header_value + )); status_code = 500; } } headers.insert(k, header_value); } Err(_) => { - self.http_content.borrow().log().warn(&format!( + self.http_content.borrow().log().warnf(format_args!( "http call response header contains non-ASCII characters header: {}", k )); } } } - self.http_content.borrow().log().warn(&format!( + self.http_content.borrow().log().debugf(format_args!( "http call end, id: {}, code: {}, normal: {}, body: {:?}", /* */ token_id, status_code, normal_response, body )); @@ -277,21 +293,25 @@ where .borrow_mut() .on_grpc_call_response(token_id, status_code, response_size) } + fn on_grpc_stream_initial_metadata(&mut self, token_id: u32, num_elements: u32) { self.http_content .borrow_mut() .on_grpc_stream_initial_metadata(token_id, num_elements) } + fn on_grpc_stream_message(&mut self, token_id: u32, message_size: usize) { self.http_content .borrow_mut() .on_grpc_stream_message(token_id, message_size) } + fn on_grpc_stream_trailing_metadata(&mut self, token_id: u32, num_elements: u32) { self.http_content .borrow_mut() .on_grpc_stream_trailing_metadata(token_id, num_elements) } + fn on_grpc_stream_close(&mut self, token_id: u32, status_code: u32) { self.http_content .borrow_mut() @@ -302,6 +322,7 @@ where self.http_content.borrow_mut().on_done() } } + impl HttpContext for PluginHttpWrapper where PluginConfig: Default + DeserializeOwned + Clone + 'static, @@ -312,13 +333,15 @@ where if self.config.is_none() { return HeaderAction::Continue; } + + let mut req_headers = MultiMap::new(); for (k, v) in self.get_http_request_headers_bytes() { match String::from_utf8(v) { Ok(header_value) => { - self.req_headers.insert(k, header_value); + req_headers.insert(k, header_value); } Err(_) => { - self.http_content.borrow().log().warn(&format!( + self.http_content.borrow().log().warnf(format_args!( "request http header contains non-ASCII characters header: {}", k )); @@ -338,7 +361,7 @@ where } self.http_content .borrow_mut() - .on_http_request_complete_headers(&self.req_headers) + .on_http_request_complete_headers(&req_headers) } fn on_http_request_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction { @@ -383,13 +406,15 @@ where if self.config.is_none() { return HeaderAction::Continue; } + + let mut res_headers = MultiMap::new(); for (k, v) in self.get_http_response_headers_bytes() { match String::from_utf8(v) { Ok(header_value) => { - self.res_headers.insert(k, header_value); + res_headers.insert(k, header_value); } Err(_) => { - self.http_content.borrow().log().warn(&format!( + self.http_content.borrow().log().warnf(format_args!( "response http header contains non-ASCII characters header: {}", k )); @@ -406,7 +431,7 @@ where } self.http_content .borrow_mut() - .on_http_response_complete_headers(&self.res_headers) + .on_http_response_complete_headers(&res_headers) } fn on_http_response_body(&mut self, body_size: usize, end_of_stream: bool) -> DataAction { diff --git a/plugins/wasm-rust/src/redis_wrapper.rs b/plugins/wasm-rust/src/redis_wrapper.rs new file mode 100644 index 0000000000..50d30a1f5c --- /dev/null +++ b/plugins/wasm-rust/src/redis_wrapper.rs @@ -0,0 +1,724 @@ +use std::{collections::HashMap, time::Duration}; + +use proxy_wasm::{hostcalls::RedisCallbackFn, types::Status}; +use redis::{Cmd, ToRedisArgs, Value}; + +use crate::{cluster_wrapper::Cluster, internal}; + +pub type RedisValueCallbackFn = dyn FnOnce(&Result, usize, u32); + +fn gen_callback(call_fn: Box) -> Box { + Box::new(move |token_id, status, response_size| { + let res = match internal::get_redis_call_response(0, response_size) { + Some(data) => match redis::parse_redis_value(&data) { + Ok(v) => Ok(v), + Err(e) => Err(e.to_string()), + }, + None => Err("response data not found".to_string()), + }; + call_fn(&res, status, token_id); + }) +} + +pub struct RedisClientBuilder { + upstream: String, + username: Option, + password: Option, + timeout: Duration, +} + +impl RedisClientBuilder { + pub fn new(cluster: &dyn Cluster, timeout: Duration) -> Self { + RedisClientBuilder { + upstream: cluster.cluster_name(), + username: None, + password: None, + timeout, + } + } + + pub fn username>(mut self, username: Option) -> Self { + self.username = username.map(|u| u.as_ref().to_string()); + self + } + + pub fn password>(mut self, password: Option) -> Self { + self.password = password.map(|p| p.as_ref().to_string()); + self + } + + pub fn build(self) -> RedisClient { + RedisClient { + upstream: self.upstream, + username: self.username, + password: self.password, + timeout: self.timeout, + } + } +} + +pub struct RedisClientConfig { + upstream: String, + username: Option, + password: Option, + timeout: Duration, +} + +impl RedisClientConfig { + pub fn new(cluster: &dyn Cluster, timeout: Duration) -> Self { + RedisClientConfig { + upstream: cluster.cluster_name(), + username: None, + password: None, + timeout, + } + } + + pub fn username>(&mut self, username: Option) -> &Self { + self.username = username.map(|u| u.as_ref().to_string()); + self + } + + pub fn password>(&mut self, password: Option) -> &Self { + self.password = password.map(|p| p.as_ref().to_string()); + self + } +} + +#[derive(Debug, Clone)] +pub struct RedisClient { + upstream: String, + username: Option, + password: Option, + timeout: Duration, +} + +impl RedisClient { + pub fn new(config: &RedisClientConfig) -> Self { + RedisClient { + upstream: config.upstream.clone(), + username: config.username.clone(), + password: config.password.clone(), + timeout: config.timeout, + } + } + + pub fn init(&self) -> Result<(), Status> { + internal::redis_init( + &self.upstream, + self.username.as_ref().map(|u| u.as_bytes()), + self.password.as_ref().map(|p| p.as_bytes()), + self.timeout, + ) + } + + fn call(&self, query: &[u8], call_fn: Box) -> Result { + internal::dispatch_redis_call(&self.upstream, query, gen_callback(call_fn)) + } + + pub fn command(&self, cmd: &Cmd, call_fn: Box) -> Result { + self.call(&cmd.get_packed_command(), call_fn) + } + + pub fn eval( + &self, + script: &str, + numkeys: i32, + keys: Vec<&str>, + args: Vec, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("eval"); + cmd.arg(script).arg(numkeys); + for key in keys { + cmd.arg(key); + } + for arg in args { + cmd.arg(arg); + } + self.command(&cmd, call_fn) + } + + // Key + pub fn del(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("del"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn exists(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("exists"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn expire( + &self, + key: &str, + ttl: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("expire"); + cmd.arg(key).arg(ttl); + self.command(&cmd, call_fn) + } + + pub fn persist(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("persist"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + // String + pub fn get(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("get"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn set( + &self, + key: &str, + value: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("set"); + cmd.arg(key).arg(value); + self.command(&cmd, call_fn) + } + + pub fn setex( + &self, + key: &str, + value: T, + ttl: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("setex"); + cmd.arg(key).arg(ttl).arg(value); + self.command(&cmd, call_fn) + } + + pub fn mget(&self, keys: Vec<&str>, call_fn: Box) -> Result { + let mut cmd = redis::cmd("mget"); + for key in keys { + cmd.arg(key); + } + self.command(&cmd, call_fn) + } + + pub fn mset( + &self, + kv_map: HashMap<&str, T>, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("mset"); + for (k, v) in kv_map { + cmd.arg(k).arg(v); + } + self.command(&cmd, call_fn) + } + + pub fn incr(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("incr"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn decr(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("decr"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn incrby( + &self, + key: &str, + delta: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("incrby"); + cmd.arg(key).arg(delta); + self.command(&cmd, call_fn) + } + + pub fn decrby( + &self, + key: &str, + delta: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("decrby"); + cmd.arg(key).arg(delta); + self.command(&cmd, call_fn) + } + + // List + pub fn llen(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("llen"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn rpush( + &self, + key: &str, + vals: Vec, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("rpush"); + cmd.arg(key); + for val in vals { + cmd.arg(val); + } + self.command(&cmd, call_fn) + } + + pub fn rpop(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("rpop"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn lpush( + &self, + key: &str, + vals: Vec, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("lpush"); + cmd.arg(key); + for val in vals { + cmd.arg(val); + } + self.command(&cmd, call_fn) + } + + pub fn lpop(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("lpop"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn lindex( + &self, + key: &str, + index: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("lindex"); + cmd.arg(key).arg(index); + self.command(&cmd, call_fn) + } + + pub fn lrange( + &self, + key: &str, + start: i32, + stop: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("lrange"); + cmd.arg(key).arg(start).arg(stop); + self.command(&cmd, call_fn) + } + + pub fn lrem( + &self, + key: &str, + count: i32, + value: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("lrem"); + cmd.arg(key).arg(count).arg(value); + self.command(&cmd, call_fn) + } + + pub fn linsert_before( + &self, + key: &str, + pivot: T, + value: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("linsert"); + cmd.arg(key).arg("before").arg(pivot).arg(value); + self.command(&cmd, call_fn) + } + + pub fn linsert_after( + &self, + key: &str, + pivot: T, + value: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("linsert"); + cmd.arg(key).arg("after").arg(pivot).arg(value); + + self.command(&cmd, call_fn) + } + + // Hash + pub fn hexists( + &self, + key: &str, + field: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hexists"); + cmd.arg(key).arg(field); + self.command(&cmd, call_fn) + } + + pub fn hdel( + &self, + key: &str, + fields: Vec<&str>, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hdel"); + cmd.arg(key); + for field in fields { + cmd.arg(field); + } + self.command(&cmd, call_fn) + } + + pub fn hlen(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("hlen"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn hget( + &self, + key: &str, + field: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hget"); + cmd.arg(key).arg(field); + self.command(&cmd, call_fn) + } + + pub fn hset( + &self, + key: &str, + field: &str, + value: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hset"); + cmd.arg(key).arg(field).arg(value); + self.command(&cmd, call_fn) + } + + pub fn hmget( + &self, + key: &str, + fields: Vec<&str>, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hmget"); + cmd.arg(key); + for field in fields { + cmd.arg(field); + } + self.command(&cmd, call_fn) + } + + pub fn hmset( + &self, + key: &str, + kv_map: HashMap<&str, T>, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hmset"); + cmd.arg(key); + for (k, v) in kv_map { + cmd.arg(k).arg(v); + } + self.command(&cmd, call_fn) + } + + pub fn hkeys(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("hkeys"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn hvals(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("hvals"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn hgetall(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("hgetall"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn hincrby( + &self, + key: &str, + field: &str, + delta: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hincrby"); + cmd.arg(key).arg(field).arg(delta); + self.command(&cmd, call_fn) + } + + pub fn hincrbyfloat( + &self, + key: &str, + field: &str, + delta: f64, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("hincrbyfloat"); + cmd.arg(key).arg(field).arg(delta); + self.command(&cmd, call_fn) + } + + // Set + pub fn scard(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("scard"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn sadd( + &self, + key: &str, + vals: Vec, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sadd"); + cmd.arg(key); + for val in vals { + cmd.arg(val); + } + self.command(&cmd, call_fn) + } + + pub fn srem( + &self, + key: &str, + vals: Vec, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("srem"); + cmd.arg(key); + for val in vals { + cmd.arg(val); + } + self.command(&cmd, call_fn) + } + + pub fn sismember( + &self, + key: &str, + value: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sismember"); + cmd.arg(key).arg(value); + self.command(&cmd, call_fn) + } + + pub fn smembers(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("smembers"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn sdiff( + &self, + key1: &str, + key2: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sdiff"); + cmd.arg(key1).arg(key2); + self.command(&cmd, call_fn) + } + + pub fn sdiffstore( + &self, + destination: &str, + key1: &str, + key2: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sdiffstore"); + cmd.arg(destination).arg(key1).arg(key2); + self.command(&cmd, call_fn) + } + + pub fn sinter( + &self, + key1: &str, + key2: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sinter"); + cmd.arg(key1).arg(key2); + self.command(&cmd, call_fn) + } + + pub fn sinterstore( + &self, + destination: &str, + key1: &str, + key2: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sinterstore"); + cmd.arg(destination).arg(key1).arg(key2); + self.command(&cmd, call_fn) + } + + pub fn sunion( + &self, + key1: &str, + key2: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sunion"); + cmd.arg(key1).arg(key2); + self.command(&cmd, call_fn) + } + + pub fn sunion_store( + &self, + destination: &str, + key1: &str, + key2: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("sunionstore"); + cmd.arg(destination).arg(key1).arg(key2); + self.command(&cmd, call_fn) + } + + // Sorted Set + pub fn zcard(&self, key: &str, call_fn: Box) -> Result { + let mut cmd = redis::cmd("zcard"); + cmd.arg(key); + self.command(&cmd, call_fn) + } + + pub fn zadd( + &self, + key: &str, + ms_map: HashMap<&str, T>, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zadd"); + cmd.arg(key); + for (m, s) in ms_map { + cmd.arg(s).arg(m); + } + self.command(&cmd, call_fn) + } + + pub fn zcount( + &self, + key: &str, + min: T, + max: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zcount"); + cmd.arg(key).arg(min).arg(max); + self.command(&cmd, call_fn) + } + + pub fn zincrby( + &self, + key: &str, + member: &str, + delta: T, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zincrby"); + cmd.arg(key).arg(delta).arg(member); + self.command(&cmd, call_fn) + } + + pub fn zscore( + &self, + key: &str, + member: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zscore"); + cmd.arg(key).arg(member); + self.command(&cmd, call_fn) + } + + pub fn zrank( + &self, + key: &str, + member: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zrank"); + cmd.arg(key).arg(member); + self.command(&cmd, call_fn) + } + + pub fn zrev_rank( + &self, + key: &str, + member: &str, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zrevrank"); + cmd.arg(key).arg(member); + self.command(&cmd, call_fn) + } + + pub fn zrem( + &self, + key: &str, + members: Vec<&str>, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zrem"); + cmd.arg(key); + for member in members { + cmd.arg(member); + } + self.command(&cmd, call_fn) + } + + pub fn zrange( + &self, + key: &str, + start: i32, + stop: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zrange"); + cmd.arg(key).arg(start).arg(stop); + self.command(&cmd, call_fn) + } + + pub fn zrevrange( + &self, + key: &str, + start: i32, + stop: i32, + call_fn: Box, + ) -> Result { + let mut cmd = redis::cmd("zrevrange"); + cmd.arg(key).arg(start).arg(stop); + self.command(&cmd, call_fn) + } +} diff --git a/plugins/wasm-rust/src/request_wrapper.rs b/plugins/wasm-rust/src/request_wrapper.rs index c9a997456c..f37fd8f498 100644 --- a/plugins/wasm-rust/src/request_wrapper.rs +++ b/plugins/wasm-rust/src/request_wrapper.rs @@ -14,6 +14,7 @@ fn get_request_head(head: &str, log_flag: &str) -> String { String::new() } } + pub fn get_request_scheme() -> String { get_request_head(":scheme", "head") } @@ -57,6 +58,7 @@ pub fn is_binary_response_body() -> bool { } false } + pub fn has_request_body() -> bool { let content_type = internal::get_http_request_header("content-type"); let content_length_str = internal::get_http_request_header("content-length"); From 9a1edcd4c8560877755a21cd4fba3b69822a8bee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 29 Oct 2024 20:27:11 +0800 Subject: [PATCH 39/64] Fix the issue where wasmplugin does not work when kingress exists (#1450) --- pkg/ingress/translation/translation.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/ingress/translation/translation.go b/pkg/ingress/translation/translation.go index 1545a1749a..bb95d57682 100644 --- a/pkg/ingress/translation/translation.go +++ b/pkg/ingress/translation/translation.go @@ -187,10 +187,9 @@ func (m *IngressTranslation) List(typ config.GroupVersionKind, namespace string) higressConfig = append(higressConfig, ingressConfig...) if m.kingressConfig != nil { kingressConfig := m.kingressConfig.List(typ, namespace) - if kingressConfig == nil { - return nil + if kingressConfig != nil { + higressConfig = append(higressConfig, kingressConfig...) } - higressConfig = append(higressConfig, kingressConfig...) } return higressConfig } From 035e81a5ca19b3ad514ef4e970aa26564485a7d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=9F=A9=E8=B4=A4=E6=B6=9B?= <601803023@qq.com> Date: Thu, 31 Oct 2024 21:36:40 +0800 Subject: [PATCH 40/64] fix: Control gateway-api Listener with global.enableGatewayAPI in Helm (#1461) --- helm/core/templates/controller-deployment.yaml | 14 +++++++------- pkg/ingress/config/ingress_config.go | 6 ++++-- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/helm/core/templates/controller-deployment.yaml b/helm/core/templates/controller-deployment.yaml index c3bc70aea9..dda26d2433 100644 --- a/helm/core/templates/controller-deployment.yaml +++ b/helm/core/templates/controller-deployment.yaml @@ -69,10 +69,12 @@ spec: fieldPath: spec.serviceAccountName - name: DOMAIN_SUFFIX value: {{ .Values.global.proxy.clusterDomain }} - - name: PILOT_ENABLE_ALPHA_GATEWAY_API - value: "true" - name: GATEWAY_NAME value: {{ include "gateway.name" . }} + - name: PILOT_ENABLE_GATEWAY_API + value: "{{ .Values.global.enableGatewayAPI }}" + - name: PILOT_ENABLE_ALPHA_GATEWAY_API + value: "{{ .Values.global.enableGatewayAPI }}" {{- if .Values.controller.env }} {{- range $key, $val := .Values.controller.env }} - name: {{ $key }} @@ -219,16 +221,14 @@ spec: - name: HIGRESS_ENABLE_ISTIO_API value: "true" {{- end }} - {{- if .Values.global.enableGatewayAPI }} - name: PILOT_ENABLE_GATEWAY_API - value: "true" + value: "false" - name: PILOT_ENABLE_ALPHA_GATEWAY_API - value: "true" + value: "false" - name: PILOT_ENABLE_GATEWAY_API_STATUS - value: "true" + value: "false" - name: PILOT_ENABLE_GATEWAY_API_DEPLOYMENT_CONTROLLER value: "false" - {{- end }} {{- if not .Values.global.enableHigressIstio }} - name: CUSTOM_CA_CERT_NAME value: "higress-ca-root-cert" diff --git a/pkg/ingress/config/ingress_config.go b/pkg/ingress/config/ingress_config.go index 9c2fc94b9b..6030ae50af 100644 --- a/pkg/ingress/config/ingress_config.go +++ b/pkg/ingress/config/ingress_config.go @@ -34,6 +34,7 @@ import ( extensions "istio.io/api/extensions/v1alpha1" networking "istio.io/api/networking/v1alpha3" istiotype "istio.io/api/type/v1beta1" + "istio.io/istio/pilot/pkg/features" istiomodel "istio.io/istio/pilot/pkg/model" "istio.io/istio/pilot/pkg/util/protoconv" "istio.io/istio/pkg/cluster" @@ -235,8 +236,9 @@ func (m *IngressConfig) AddLocalCluster(options common.Options) { ingressController = ingressv1.NewController(m.localKubeClient, m.localKubeClient, options, secretController) } m.remoteIngressControllers[options.ClusterId] = ingressController - - m.remoteGatewayControllers[options.ClusterId] = gateway.NewController(m.localKubeClient, options) + if features.EnableGatewayAPI { + m.remoteGatewayControllers[options.ClusterId] = gateway.NewController(m.localKubeClient, options) + } } func (m *IngressConfig) List(typ config.GroupVersionKind, namespace string) []config.Config { From 63d5422da6ddce077451f07f22d33ca940f68f00 Mon Sep 17 00:00:00 2001 From: littlejian Date: Fri, 1 Nov 2024 22:39:19 +0800 Subject: [PATCH 41/64] feat:Support downstream and upstram, which can be configured through helm parameters (#1399) --- helm/core/templates/configmap.yaml | 6 ++++++ helm/core/values.yaml | 16 ++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/helm/core/templates/configmap.yaml b/helm/core/templates/configmap.yaml index b7814f5bf7..a915604d15 100644 --- a/helm/core/templates/configmap.yaml +++ b/helm/core/templates/configmap.yaml @@ -116,6 +116,12 @@ data: {{- $existingData = index $existingConfig.data "higress" | default "{}" | fromYaml }} {{- end }} {{- $newData := dict }} + {{- if hasKey .Values "upstream" }} + {{- $_ := set $newData "upstream" .Values.upstream }} + {{- end }} + {{- if hasKey .Values "downstream" }} + {{- $_ := set $newData "downstream" .Values.downstream }} + {{- end }} {{- if and (hasKey .Values "tracing") .Values.tracing.enable }} {{- $_ := set $newData "tracing" .Values.tracing }} {{- end }} diff --git a/helm/core/values.yaml b/helm/core/values.yaml index 39582f748d..fb03a2f85f 100644 --- a/helm/core/values.yaml +++ b/helm/core/values.yaml @@ -684,3 +684,19 @@ tracing: # zipkin: # service: "" # port: 9411 + +# Downstream config settings +downstream: + idleTimeout: 180 + maxRequestHeadersKb: 60 + connectionBufferLimits: 32768 + http2: + maxConcurrentStreams: 100 + initialStreamWindowSize: 65535 + initialConnectionWindowSize: 1048576 + routeTimeout: 0 + +# Upstream config settings +upstream: + idleTimeout: 10 + connectionBufferLimits: 10485760 From 7e8b0445ad9ce23d921068605b502f5f8cc8535c Mon Sep 17 00:00:00 2001 From: rinfx <893383980@qq.com> Date: Mon, 4 Nov 2024 22:04:20 +0800 Subject: [PATCH 42/64] modify log-format, then every plugin log is associated with access log (#1454) --- plugins/wasm-go/pkg/wrapper/log_wrapper.go | 14 ++++++++++++-- plugins/wasm-go/pkg/wrapper/plugin_wrapper.go | 2 ++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/plugins/wasm-go/pkg/wrapper/log_wrapper.go b/plugins/wasm-go/pkg/wrapper/log_wrapper.go index 6c6312f31a..65c0aa346c 100644 --- a/plugins/wasm-go/pkg/wrapper/log_wrapper.go +++ b/plugins/wasm-go/pkg/wrapper/log_wrapper.go @@ -36,7 +36,12 @@ type Log struct { } func (l Log) log(level LogLevel, msg string) { - msg = fmt.Sprintf("[%s] %s", l.pluginName, msg) + requestIDRaw, _ := proxywasm.GetProperty([]string{"x_request_id"}) + requestID := string(requestIDRaw) + if requestID == "" { + requestID = "nil" + } + msg = fmt.Sprintf("[%s] [%s] %s", l.pluginName, requestID, msg) switch level { case LogLevelTrace: proxywasm.LogTrace(msg) @@ -54,7 +59,12 @@ func (l Log) log(level LogLevel, msg string) { } func (l Log) logFormat(level LogLevel, format string, args ...interface{}) { - format = fmt.Sprintf("[%s] %s", l.pluginName, format) + requestIDRaw, _ := proxywasm.GetProperty([]string{"x_request_id"}) + requestID := string(requestIDRaw) + if requestID == "" { + requestID = "nil" + } + format = fmt.Sprintf("[%s] [%s] %s", l.pluginName, requestID, format) switch level { case LogLevelTrace: proxywasm.LogTracef(format, args...) diff --git a/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go b/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go index 19a2357aca..e1984dc991 100644 --- a/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go +++ b/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go @@ -370,6 +370,8 @@ func (ctx *CommonHttpCtx[PluginConfig]) SetResponseBodyBufferLimit(size uint32) } func (ctx *CommonHttpCtx[PluginConfig]) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { + requestID, _ := proxywasm.GetHttpRequestHeader("x-request-id") + _ = proxywasm.SetProperty([]string{"x_request_id"}, []byte(requestID)) config, err := ctx.plugin.GetMatchConfig() if err != nil { ctx.plugin.vm.log.Errorf("get match config failed, err:%v", err) From c1f2504e8781a2191d454b5486841900439d8922 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Tue, 5 Nov 2024 15:26:55 +0800 Subject: [PATCH 43/64] Ai data mask deny word match optimize (#1453) --- .../extensions/ai-data-masking/Cargo.toml | 2 +- .../extensions/ai-data-masking/README.md | 2 +- .../extensions/ai-data-masking/README_EN.md | 1 + .../ai-data-masking/src/deny_word.rs | 54 ++++++++++++++++++ .../extensions/ai-data-masking/src/lib.rs | 57 +++---------------- .../tests/rust-wasm-ai-data-masking.go | 6 ++ .../tests/rust-wasm-ai-data-masking.yaml | 6 ++ 7 files changed, 78 insertions(+), 50 deletions(-) create mode 100644 plugins/wasm-rust/extensions/ai-data-masking/src/deny_word.rs diff --git a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.toml b/plugins/wasm-rust/extensions/ai-data-masking/Cargo.toml index b8bd6df5b2..aa7372fb20 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.toml +++ b/plugins/wasm-rust/extensions/ai-data-masking/Cargo.toml @@ -18,5 +18,5 @@ md5 = "0" grok = "2" lazy_static = "1" jieba-rs = "0" -rust-embed="8.5.0" +rust-embed = "8.5.0" jsonpath-rust = "0" diff --git a/plugins/wasm-rust/extensions/ai-data-masking/README.md b/plugins/wasm-rust/extensions/ai-data-masking/README.md index b892b3dab8..c1e7f73369 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/README.md +++ b/plugins/wasm-rust/extensions/ai-data-masking/README.md @@ -148,4 +148,4 @@ curl -X POST \ - 流模式中,如果敏感词语被多个chunk拆分,可能会有敏感词的一部分返回给用户的情况 - grok 内置规则列表 https://help.aliyun.com/zh/sls/user-guide/grok-patterns - 内置敏感词库数据来源 https://github.com/houbb/sensitive-word/tree/master/src/main/resources - + - 由于敏感词列表是在文本分词后进行匹配的,所以请将 `deny_words` 设置为单个单词,英文多单词情况如 `hello word` 可能无法匹配 diff --git a/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md b/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md index 45e1622875..d7cb72378e 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md +++ b/plugins/wasm-rust/extensions/ai-data-masking/README_EN.md @@ -129,3 +129,4 @@ Please note that you need to replace `"key":"value"` with the actual data conten - In streaming mode, if sensitive words are split across multiple chunks, there may be cases where part of the sensitive word is returned to the user - Grok built-in rule list: https://help.aliyun.com/zh/sls/user-guide/grok-patterns - Built-in sensitive word library data source: https://github.com/houbb/sensitive-word/tree/master/src/main/resources + - Since the sensitive word list is matched after tokenizing the text, please set `deny_words` to single words. In the case of multiple words in English, such as `hello world`, the match may not be successful. diff --git a/plugins/wasm-rust/extensions/ai-data-masking/src/deny_word.rs b/plugins/wasm-rust/extensions/ai-data-masking/src/deny_word.rs new file mode 100644 index 0000000000..99ecab4d5b --- /dev/null +++ b/plugins/wasm-rust/extensions/ai-data-masking/src/deny_word.rs @@ -0,0 +1,54 @@ +use std::collections::HashSet; + +use jieba_rs::Jieba; + +use crate::Asset; + +#[derive(Default, Debug, Clone)] +pub(crate) struct DenyWord { + jieba: Jieba, + words: HashSet, +} + +impl DenyWord { + pub(crate) fn from_iter>>(words: T) -> Self { + let mut deny_word = DenyWord::default(); + + for word in words { + let word_s = word.into(); + let w = word_s.trim(); + if w.is_empty() { + continue; + } + deny_word.jieba.add_word(w, None, None); + deny_word.words.insert(w.to_string()); + } + + deny_word + } + + pub(crate) fn empty() -> Self { + DenyWord { + jieba: Jieba::empty(), + words: HashSet::new(), + } + } + + pub(crate) fn system() -> Self { + if let Some(file) = Asset::get("sensitive_word_dict.txt") { + if let Ok(data) = std::str::from_utf8(file.data.as_ref()) { + return DenyWord::from_iter(data.split('\n')); + } + } + Self::empty() + } + + pub(crate) fn check(&self, message: &str) -> Option { + for word in self.jieba.cut(message, true) { + if self.words.contains(word) { + return Some(word.to_string()); + } + } + None + } +} diff --git a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs index 2f2999d673..ca2db3da42 100644 --- a/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs +++ b/plugins/wasm-rust/extensions/ai-data-masking/src/lib.rs @@ -12,13 +12,15 @@ // See the License for the specific language governing permissions and // limitations under the License. +mod deny_word; + +use crate::deny_word::DenyWord; use fancy_regex::Regex; use grok::patterns; use higress_wasm_rust::log::Log; use higress_wasm_rust::plugin_wrapper::{HttpContextWrapper, RootContextWrapper}; use higress_wasm_rust::request_wrapper::has_request_body; use higress_wasm_rust::rule_matcher::{on_configure, RuleMatcher, SharedRuleMatcher}; -use jieba_rs::Jieba; use jsonpath_rust::{JsonPath, JsonPathValue}; use lazy_static::lazy_static; use proxy_wasm::traits::{Context, HttpContext, RootContext}; @@ -29,7 +31,7 @@ use serde::Deserialize; use serde::Deserializer; use serde_json::{json, Value}; use std::cell::RefCell; -use std::collections::{BTreeMap, HashMap, HashSet, VecDeque}; +use std::collections::{BTreeMap, HashMap, VecDeque}; use std::ops::DerefMut; use std::rc::Rc; use std::str::FromStr; @@ -47,11 +49,6 @@ const GROK_PATTERN: &str = r"%\{(?(?[A-z0-9]+)(?::(?[A-z0- #[folder = "res/"] struct Asset; -#[derive(Default, Debug, Clone)] -struct DenyWord { - jieba: Jieba, - words: HashSet, -} struct System { deny_word: DenyWord, grok_regex: Regex, @@ -227,52 +224,12 @@ static SYSTEM_PATTERNS: &[(&str, &str)] = &[ ("IDCARD", r#"\d{17}[0-9xX]|\d{15}"#), ]; -impl DenyWord { - fn empty() -> Self { - DenyWord { - jieba: Jieba::empty(), - words: HashSet::new(), - } - } - fn from_iter>>(words: T) -> Self { - let mut deny_word = DenyWord::empty(); - - for word in words { - let word_s = word.into(); - let w = word_s.trim(); - if w.is_empty() { - continue; - } - deny_word.jieba.add_word(w, None, None); - deny_word.words.insert(w.to_string()); - } - - deny_word - } - fn default() -> Self { - if let Some(file) = Asset::get("sensitive_word_dict.txt") { - if let Ok(data) = std::str::from_utf8(file.data.as_ref()) { - return DenyWord::from_iter(data.split('\n')); - } - } - DenyWord::empty() - } - - fn check(&self, message: &str) -> Option { - for word in self.jieba.cut(message, true) { - if self.words.contains(word) { - return Some(word.to_string()); - } - } - None - } -} impl System { fn new() -> Self { let grok_regex = Regex::new(GROK_PATTERN).unwrap(); let grok_patterns = BTreeMap::new(); let mut system = System { - deny_word: DenyWord::default(), + deny_word: DenyWord::system(), grok_regex, grok_patterns, }; @@ -335,6 +292,7 @@ impl System { (ret, ok) } } + impl AiDataMaskingRoot { fn new() -> Self { AiDataMaskingRoot { @@ -382,6 +340,7 @@ impl RootContextWrapper for AiDataMaskingRoot { })) } } + impl AiDataMasking { fn check_message(&self, message: &str) -> bool { if let Some(config) = &self.config { @@ -532,6 +491,7 @@ impl AiDataMasking { } impl Context for AiDataMasking {} + impl HttpContext for AiDataMasking { fn on_http_request_headers( &mut self, @@ -607,6 +567,7 @@ impl HttpContext for AiDataMasking { DataAction::Continue } } + impl HttpContextWrapper for AiDataMasking { fn log(&self) -> &Log { &self.log diff --git a/test/e2e/conformance/tests/rust-wasm-ai-data-masking.go b/test/e2e/conformance/tests/rust-wasm-ai-data-masking.go index 9c1469a826..3b49c2c6ae 100644 --- a/test/e2e/conformance/tests/rust-wasm-ai-data-masking.go +++ b/test/e2e/conformance/tests/rust-wasm-ai-data-masking.go @@ -153,6 +153,12 @@ var RustWasmPluginsAiDataMasking = suite.ConformanceTest{ []byte("test"), []byte("{\"errmsg\":\"提问或回答中包含敏感词,已被屏蔽\"}"), )) + testcases = append(testcases, gen_assertion( + "system_no_deny.raw.com", + false, + []byte("test"), + []byte("{\"res\":\"工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作\"}"), + )) testcases = append(testcases, gen_assertion( "costom_word1.raw.com", false, diff --git a/test/e2e/conformance/tests/rust-wasm-ai-data-masking.yaml b/test/e2e/conformance/tests/rust-wasm-ai-data-masking.yaml index 9678ae3b79..71d7d620f7 100644 --- a/test/e2e/conformance/tests/rust-wasm-ai-data-masking.yaml +++ b/test/e2e/conformance/tests/rust-wasm-ai-data-masking.yaml @@ -100,6 +100,12 @@ spec: headers: - Content-Type=application/json "body": "{\"res\":\"fuck\"}" + - domain: + - system_no_deny.raw.com + config: + headers: + - Content-Type=application/json + "body": "{\"res\":\"工信处女干事每月经过下属科室都要亲口交代24口交换机等技术性器件的安装工作\"}" - domain: - costom_word1.raw.com config: From 548cf2f081ecba72c6b1d379610241a288bece1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Tue, 5 Nov 2024 20:59:51 +0800 Subject: [PATCH 44/64] move nottinygc to proxy-wasm-go-sdk (#1477) --- plugins/wasm-go/go.mod | 4 ++-- plugins/wasm-go/go.sum | 12 ++---------- plugins/wasm-go/pkg/wrapper/plugin_wrapper.go | 6 ------ 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/plugins/wasm-go/go.mod b/plugins/wasm-go/go.mod index 6373ff646e..2b21cb8360 100644 --- a/plugins/wasm-go/go.mod +++ b/plugins/wasm-go/go.mod @@ -4,8 +4,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-20240711023527-ba358c48772f + github.com/higress-group/proxy-wasm-go-sdk v1.0.0 github.com/stretchr/testify v1.8.4 github.com/tidwall/gjson v1.17.3 github.com/tidwall/resp v0.1.1 @@ -13,6 +12,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/higress-group/nottinygc v0.0.0-20231101025119-e93c4c2f8520 // indirect github.com/magefile/mage v1.14.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/tidwall/match v1.1.1 // indirect diff --git a/plugins/wasm-go/go.sum b/plugins/wasm-go/go.sum index f396d4d7d9..679d810a90 100644 --- a/plugins/wasm-go/go.sum +++ b/plugins/wasm-go/go.sum @@ -4,22 +4,14 @@ 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-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/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/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f h1:ZIiIBRvIw62gA5MJhuwp1+2wWbqL9IGElQ499rUsYYg= -github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f/go.mod h1:hNFjhrLUIq+kJ9bOcs8QtiplSQ61GZXtd2xHKx4BYRo= +github.com/higress-group/proxy-wasm-go-sdk v1.0.0 h1:BZRNf4R7jr9hwRivg/E29nkVaKEak5MWjBDhWjuHijU= +github.com/higress-group/proxy-wasm-go-sdk v1.0.0/go.mod h1:iiSyFbo+rAtbtGt/bsefv8GU57h9CCLYGJA74/tF5/0= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= -github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= github.com/tidwall/gjson v1.17.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= diff --git a/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go b/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go index e1984dc991..cce0456f8c 100644 --- a/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go +++ b/plugins/wasm-go/pkg/wrapper/plugin_wrapper.go @@ -24,14 +24,8 @@ import ( "github.com/tidwall/gjson" "github.com/alibaba/higress/plugins/wasm-go/pkg/matcher" - _ "github.com/higress-group/nottinygc" ) -//export sched_yield -func sched_yield() int32 { - return 0 -} - type HttpContext interface { Scheme() string Host() string From 00cac813e376918916e20353c22f564bf3ea35b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Wed, 6 Nov 2024 13:37:18 +0800 Subject: [PATCH 45/64] add clusterrole for gateway api (#1480) --- helm/core/templates/controller-clusterrole.yaml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/helm/core/templates/controller-clusterrole.yaml b/helm/core/templates/controller-clusterrole.yaml index 8c05467e72..1f0f574555 100644 --- a/helm/core/templates/controller-clusterrole.yaml +++ b/helm/core/templates/controller-clusterrole.yaml @@ -129,3 +129,10 @@ rules: - apiGroups: ["networking.internal.knative.dev"] resources: ["ingresses/status"] verbs: ["get","patch","update"] + # gateway api need + - apiGroups: ["apps"] + verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ] + resources: [ "deployments" ] + - apiGroups: [""] + verbs: [ "get", "watch", "list", "update", "patch", "create", "delete" ] + resources: [ "serviceaccounts"] From 9b995321bb7f9df3aa0bedda42f262bb05721786 Mon Sep 17 00:00:00 2001 From: mamba <371510756@qq.com> Date: Thu, 7 Nov 2024 09:11:57 +0800 Subject: [PATCH 46/64] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E7=89=88=E6=9C=AC=E8=83=BD=E5=8A=9B=EF=BC=9A=E6=A0=B9?= =?UTF-8?q?=E6=8D=AE=E4=B8=8D=E5=90=8C=E8=B7=AF=E7=94=B1=E6=98=A0=E5=B0=84?= =?UTF-8?q?=E4=B8=8D=E5=90=8C=E7=9A=84`Version`=E7=89=88=E6=9C=AC=E3=80=82?= =?UTF-8?q?=20(#1429)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../extensions/frontend-gray/README.md | 8 ++- .../extensions/frontend-gray/config/config.go | 27 +++++---- .../wasm-go/extensions/frontend-gray/main.go | 57 +++++++++++-------- .../extensions/frontend-gray/util/utils.go | 37 ++++++++++++ 4 files changed, 90 insertions(+), 39 deletions(-) diff --git a/plugins/wasm-go/extensions/frontend-gray/README.md b/plugins/wasm-go/extensions/frontend-gray/README.md index 870100eb2f..c542930621 100644 --- a/plugins/wasm-go/extensions/frontend-gray/README.md +++ b/plugins/wasm-go/extensions/frontend-gray/README.md @@ -57,15 +57,17 @@ description: 前端灰度插件配置参考 | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | |----------------|--------------|------|-----|-----------------------------------------------------------------------------------| -| `version` | string | 必填 | - | Base版本的版本号,作为兜底的版本 | +| `version` | string | 必填 | - | Base版本的版本号,作为兜底的版本 | +| `versionPredicates` | string | 必填 | - | 和`version`含义相同,但是满足多版本的需求:根据不同路由映射不同的`Version`版本。一般用于微前端的场景:一个主应用需要管理多个微应用 | `grayDeployments`字段配置说明: | 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | |--------|--------|------|-----|-------------------------------------------------| -| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` | +| `version` | string | 必填 | - | Gray版本的版本号,如果命中灰度规则,则使用此版本。如果是非CDN部署,在header添加`x-higress-tag` | +| `versionPredicates` | string | 必填 | - | 和`version`含义相同,但是满足多版本的需求:根据不同路由映射不同的`Version`版本。一般用于微前端的场景:一个主应用需要管理多个微应用 | | `backendVersion` | string | 必填 | - | 后端灰度版本,配合`key`为`${backendGrayTag}`,写入cookie中 | -| `name` | string | 必填 | - | 规则名称和`rules[].name`关联, | +| `name` | string | 必填 | - | 规则名称和`rules[].name`关联 | | `enabled` | boolean | 必填 | - | 是否启动当前灰度规则 | | `weight` | int | 非必填 | - | 按照比例灰度,比如`50`。注意:灰度规则权重总和不能超过100,如果同时配置了`grayKey`以及`grayDeployments[0].weight`按照比例灰度优先生效 | > 为了实现按比例(weight) 进行灰度发布,并确保用户粘滞,我们需要确认客户端的唯一性。如果配置了 grayKey,则将其用作唯一标识;如果未配置 grayKey,则使用客户端的访问 IP 地址作为唯一标识。 diff --git a/plugins/wasm-go/extensions/frontend-gray/config/config.go b/plugins/wasm-go/extensions/frontend-gray/config/config.go index 25596fefef..4a56821d20 100644 --- a/plugins/wasm-go/extensions/frontend-gray/config/config.go +++ b/plugins/wasm-go/extensions/frontend-gray/config/config.go @@ -25,11 +25,12 @@ type GrayRule struct { } type Deployment struct { - Name string - Enabled bool - Version string - BackendVersion string - Weight int + Name string + Enabled bool + Version string + BackendVersion string + Weight int + VersionPredicates map[string]string } type Rewrite struct { @@ -129,8 +130,9 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { grayDeployments := json.Get("grayDeployments").Array() grayConfig.BaseDeployment = &Deployment{ - Name: baseDeployment.Get("name").String(), - Version: strings.Trim(baseDeployment.Get("version").String(), " "), + Name: baseDeployment.Get("name").String(), + Version: strings.Trim(baseDeployment.Get("version").String(), " "), + VersionPredicates: convertToStringMap(baseDeployment.Get("versionPredicates")), } for _, item := range grayDeployments { if !item.Get("enabled").Bool() { @@ -138,11 +140,12 @@ func JsonToGrayConfig(json gjson.Result, grayConfig *GrayConfig) { } grayWeight := int(item.Get("weight").Int()) grayConfig.GrayDeployments = append(grayConfig.GrayDeployments, &Deployment{ - Name: item.Get("name").String(), - Enabled: item.Get("enabled").Bool(), - Version: strings.Trim(item.Get("version").String(), " "), - BackendVersion: item.Get("backendVersion").String(), - Weight: grayWeight, + Name: item.Get("name").String(), + Enabled: item.Get("enabled").Bool(), + Version: strings.Trim(item.Get("version").String(), " "), + BackendVersion: item.Get("backendVersion").String(), + Weight: grayWeight, + VersionPredicates: convertToStringMap(item.Get("versionPredicates")), }) grayConfig.TotalGrayWeight += grayWeight } diff --git a/plugins/wasm-go/extensions/frontend-gray/main.go b/plugins/wasm-go/extensions/frontend-gray/main.go index c5e738eeac..2cb4a5f9fe 100644 --- a/plugins/wasm-go/extensions/frontend-gray/main.go +++ b/plugins/wasm-go/extensions/frontend-gray/main.go @@ -70,22 +70,28 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, grayConfig config.GrayConfig, } // 如果没有配置比例,则进行灰度规则匹配 - if isPageRequest { - if grayConfig.TotalGrayWeight > 0 { - log.Infof("grayConfig.TotalGrayWeight: %v", grayConfig.TotalGrayWeight) - deployment = util.FilterGrayWeight(&grayConfig, preVersion, preUniqueClientId, uniqueClientId) + if util.IsSupportMultiVersion(grayConfig) { + deployment = util.FilterMultiVersionGrayRule(&grayConfig, grayKeyValue, requestPath) + log.Infof("multi version %v", deployment) + } else { + if isPageRequest { + if grayConfig.TotalGrayWeight > 0 { + log.Infof("grayConfig.TotalGrayWeight: %v", grayConfig.TotalGrayWeight) + deployment = util.FilterGrayWeight(&grayConfig, preVersion, preUniqueClientId, uniqueClientId) + } else { + deployment = util.FilterGrayRule(&grayConfig, grayKeyValue) + } + log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId) } else { - deployment = util.FilterGrayRule(&grayConfig, grayKeyValue) + grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue) + deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest) } - log.Infof("index deployment: %v, path: %v, backend: %v, xPreHigressVersion: %s,%s", deployment, requestPath, deployment.BackendVersion, preVersion, preUniqueClientId) - } else { - grayDeployment := util.FilterGrayRule(&grayConfig, grayKeyValue) - deployment = util.GetVersion(grayConfig, grayDeployment, preVersion, isPageRequest) + ctx.SetContext(config.XPreHigressTag, deployment.Version) + ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion) } + proxywasm.AddHttpRequestHeader(config.XHigressTag, deployment.Version) - ctx.SetContext(config.XPreHigressTag, deployment.Version) - ctx.SetContext(grayConfig.BackendGrayTag, deployment.BackendVersion) ctx.SetContext(config.IsPageRequest, isPageRequest) ctx.SetContext(config.XUniqueClientId, uniqueClientId) @@ -167,18 +173,24 @@ func onHttpResponseHeader(ctx wrapper.HttpContext, grayConfig config.GrayConfig, log.Errorf("error status: %s, error message: %v", status, err) return types.ActionContinue } + cacheControl, _ := proxywasm.GetHttpResponseHeader("cache-control") + if !strings.Contains(cacheControl, "no-cache") { + proxywasm.ReplaceHttpResponseHeader("cache-control", "no-cache, no-store, max-age=0, must-revalidate") + } - proxywasm.ReplaceHttpResponseHeader("Cache-Control", "no-cache, no-store, max-age=0, must-revalidate") - - frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) - xUniqueClient := ctx.GetContext(config.XUniqueClientId).(string) + frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string) + xUniqueClient, isUniqClientOk := ctx.GetContext(config.XUniqueClientId).(string) // 设置前端的版本 - proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge)) + if isFeVersionOk && isUniqClientOk && frontendVersion != "" { + proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s,%s; Max-Age=%s; Path=/;", config.XPreHigressTag, frontendVersion, xUniqueClient, grayConfig.UserStickyMaxAge)) + } // 设置后端的版本 if util.IsBackendGrayEnabled(grayConfig) { - backendVersion := ctx.GetContext(grayConfig.BackendGrayTag).(string) - proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%s; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.UserStickyMaxAge)) + backendVersion, isBackVersionOk := ctx.GetContext(grayConfig.BackendGrayTag).(string) + if isBackVersionOk && backendVersion != "" { + proxywasm.AddHttpResponseHeader("Set-Cookie", fmt.Sprintf("%s=%s; Max-Age=%s; Path=/;", grayConfig.BackendGrayTag, backendVersion, grayConfig.UserStickyMaxAge)) + } } return types.ActionContinue } @@ -188,16 +200,13 @@ func onHttpResponseBody(ctx wrapper.HttpContext, grayConfig config.GrayConfig, b if !enabledGray { return types.ActionContinue } - isPageRequest, ok := ctx.GetContext(config.IsPageRequest).(bool) - if !ok { - isPageRequest = false // 默认值 - } + isPageRequest, isPageRequestOk := ctx.GetContext(config.IsPageRequest).(bool) + frontendVersion, isFeVersionOk := ctx.GetContext(config.XPreHigressTag).(string) // 只处理首页相关请求 - if !isPageRequest { + if !isFeVersionOk || !isPageRequestOk || !isPageRequest { return types.ActionContinue } - frontendVersion := ctx.GetContext(config.XPreHigressTag).(string) isNotFound, ok := ctx.GetContext(config.IsNotFound).(bool) if !ok { isNotFound = false // 默认值 diff --git a/plugins/wasm-go/extensions/frontend-gray/util/utils.go b/plugins/wasm-go/extensions/frontend-gray/util/utils.go index da93c68212..a1a62a8fd9 100644 --- a/plugins/wasm-go/extensions/frontend-gray/util/utils.go +++ b/plugins/wasm-go/extensions/frontend-gray/util/utils.go @@ -276,6 +276,43 @@ func GetGrayKey(grayKeyValueByCookie string, grayKeyValueByHeader string, graySu return grayKeyValue } +// 如果基础部署或任何灰度部署中包含VersionPredicates,则认为是多版本配置 +func IsSupportMultiVersion(grayConfig config.GrayConfig) bool { + if len(grayConfig.BaseDeployment.VersionPredicates) > 0 { + return true + } + for _, deployment := range grayConfig.GrayDeployments { + if len(deployment.VersionPredicates) > 0 { + return true + } + } + return false +} + +// FilterMultiVersionGrayRule 过滤多版本灰度规则 +func FilterMultiVersionGrayRule(grayConfig *config.GrayConfig, grayKeyValue string, requestPath string) *config.Deployment { + // 首先根据灰度键值获取当前部署 + currentDeployment := FilterGrayRule(grayConfig, grayKeyValue) + + // 创建一个新的部署对象,初始化版本为当前部署的版本 + deployment := &config.Deployment{ + Version: currentDeployment.Version, + } + + // 对版本谓词的键进行排序 + keys := SortKeysByLengthAndLexicographically(currentDeployment.VersionPredicates) + + // 遍历排序后的键 + for _, prefix := range keys { + // 如果请求路径以当前前缀开头 + if strings.HasPrefix(requestPath, prefix) { + deployment.Version = currentDeployment.VersionPredicates[prefix] + return deployment + } + } + return deployment +} + // FilterGrayRule 过滤灰度规则 func FilterGrayRule(grayConfig *config.GrayConfig, grayKeyValue string) *config.Deployment { for _, deployment := range grayConfig.GrayDeployments { From 1ab69fcf82a7f273098252a574fe2d316da80219 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 7 Nov 2024 11:20:25 +0800 Subject: [PATCH 47/64] Update README.md --- plugins/wasm-go/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/plugins/wasm-go/README.md b/plugins/wasm-go/README.md index ab3f1d85e6..41fd06d6e9 100644 --- a/plugins/wasm-go/README.md +++ b/plugins/wasm-go/README.md @@ -55,6 +55,9 @@ output wasm file: extensions/request-block/plugin.wasm tinygo build -o main.wasm -scheduler=none -target=wasi -gc=custom -tags='custommalloc nottinygc_finalizer' ./extensions/request-block/main.go ``` +详细的编译说明,包括要使用更复杂的 Header 状态管理机制,请参考[ Go 开发插件的最佳实践](https://higress.io/docs/latest/user/wasm-go/#3-%E7%BC%96%E8%AF%91%E7%94%9F%E6%88%90-wasm-%E6%96%87%E4%BB%B6)。 + + ### step2. 构建并推送插件的 docker 镜像 使用这份简单的 Dockerfile @@ -201,4 +204,4 @@ cSuite.Setup(t) ```bash PLUGIN_NAME=request-block make higress-wasmplugin-test -``` \ No newline at end of file +``` From 73cf32aadd7959a698a2f16cf8539c2c51979798 Mon Sep 17 00:00:00 2001 From: Kent Dong Date: Thu, 7 Nov 2024 11:40:57 +0800 Subject: [PATCH 48/64] feat: Update istio codebase (#1485) --- istio/istio | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/istio/istio b/istio/istio index 1dbd773596..ce6a5d5934 160000 --- a/istio/istio +++ b/istio/istio @@ -1 +1 @@ -Subproject commit 1dbd77359624ab4af2953a4840927fbd7ea1d668 +Subproject commit ce6a5d59348fca11729cfcd6aa016aba9f8cd784 From aee37c5e22670b11ba88d1939b8c77dde0f9da68 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Thu, 7 Nov 2024 20:20:42 +0800 Subject: [PATCH 49/64] Implement Rust Wasm Plugin Build & Publish Action (#1483) --- .../build-and-push-wasm-plugin-image.yaml | 46 ++++++++++++++++--- plugins/wasm-rust/DockerfileBuilder | 38 +++++++++++++++ plugins/wasm-rust/Makefile | 18 ++++++++ 3 files changed, 95 insertions(+), 7 deletions(-) create mode 100644 plugins/wasm-rust/DockerfileBuilder diff --git a/.github/workflows/build-and-push-wasm-plugin-image.yaml b/.github/workflows/build-and-push-wasm-plugin-image.yaml index 518dfeca75..ef6de46c8d 100644 --- a/.github/workflows/build-and-push-wasm-plugin-image.yaml +++ b/.github/workflows/build-and-push-wasm-plugin-image.yaml @@ -3,9 +3,16 @@ name: Build and Push Wasm Plugin Image on: push: tags: - - "wasm-go-*-v*.*.*" # 匹配 wasm-go-{pluginName}-vX.Y.Z 格式的标签 + - "wasm-*-*-v*.*.*" # 匹配 wasm-{go|rust}-{pluginName}-vX.Y.Z 格式的标签 workflow_dispatch: inputs: + plugin_type: + description: 'Type of the plugin' + required: true + type: choice + options: + - go + - rust plugin_name: description: 'Name of the plugin' required: true @@ -23,32 +30,40 @@ jobs: env: IMAGE_REGISTRY_SERVICE: ${{ vars.IMAGE_REGISTRY || 'higress-registry.cn-hangzhou.cr.aliyuncs.com' }} IMAGE_REPOSITORY: ${{ vars.PLUGIN_IMAGE_REPOSITORY || 'plugins' }} + RUST_VERSION: 1.82 GO_VERSION: 1.19 TINYGO_VERSION: 0.28.1 ORAS_VERSION: 1.0.0 steps: - - name: Set plugin_name and version from inputs or ref_name + - name: Set plugin_type, plugin_name and version from inputs or ref_name id: set_vars run: | if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + plugin_type="${{ github.event.inputs.plugin_type }}" plugin_name="${{ github.event.inputs.plugin_name }}" version="${{ github.event.inputs.version }}" + builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-rust-builder:rust${{ env.RUST_VERSION }}-oras${{ env.ORAS_VERSION }}" else ref_name=${{ github.ref_name }} + plugin_type=${ref_name#*-} # 删除插件类型前面的字段(wasm-) + plugin_type=${plugin_type%-*} # 删除插件类型后面的字段(-{plugin_name}-vX.Y.Z) plugin_name=${ref_name#*-*-} # 删除插件名前面的字段(wasm-go-) plugin_name=${plugin_name%-*} # 删除插件名后面的字段(-vX.Y.Z) version=$(echo "$ref_name" | awk -F'v' '{print $2}') + builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-tinygo${{ env.TINYGO_VERSION }}-oras${{ env.ORAS_VERSION }}" fi + echo "PLUGIN_TYPE=$plugin_type" >> $GITHUB_ENV echo "PLUGIN_NAME=$plugin_name" >> $GITHUB_ENV echo "VERSION=$version" >> $GITHUB_ENV + echo "BUILDER_IMAGE=$builder_image" >> $GITHUB_ENV - name: Checkout code uses: actions/checkout@v3 - name: File Check run: | - workspace=${{ github.workspace }}/plugins/wasm-go/extensions/${PLUGIN_NAME} + workspace=${{ github.workspace }}/plugins/wasm-${PLUGIN_TYPE}/extensions/${PLUGIN_NAME} push_command="./plugin.tar.gz:application/vnd.oci.image.layer.v1.tar+gzip" # 查找spec.yaml @@ -75,10 +90,10 @@ jobs: echo "PUSH_COMMAND=\"$push_command\"" >> $GITHUB_ENV - - name: Run a wasm-go-builder + - name: Run a wasm-builder env: PLUGIN_NAME: ${{ env.PLUGIN_NAME }} - BUILDER_IMAGE: higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-tinygo${{ env.TINYGO_VERSION }}-oras${{ env.ORAS_VERSION }} + BUILDER_IMAGE: ${{ env.BUILDER_IMAGE }} run: | docker run -itd --name builder -v ${{ github.workspace }}:/workspace -e PLUGIN_NAME=${{ env.PLUGIN_NAME }} --rm ${{ env.BUILDER_IMAGE }} /bin/bash @@ -93,7 +108,7 @@ jobs: echo "TargetImage=${target_image}" echo "TargetImageLatest=${target_image_latest}" - cd ${{ github.workspace }}/plugins/wasm-go/extensions/${PLUGIN_NAME} + cd ${{ github.workspace }}/plugins/wasm-${PLUGIN_TYPE}/extensions/${PLUGIN_NAME} if [ -f ./.buildrc ]; then echo 'Found .buildrc file, sourcing it...' . ./.buildrc @@ -101,7 +116,7 @@ jobs: echo '.buildrc file not found' fi echo "EXTRA_TAGS=${EXTRA_TAGS}" - + if [ "${PLUGIN_TYPE}" == "go" ]; then command=" set -e cd /workspace/plugins/wasm-go/extensions/${PLUGIN_NAME} @@ -112,4 +127,21 @@ jobs: oras push ${target_image} ${push_command} oras push ${target_image_latest} ${push_command} " + elif [ "${PLUGIN_TYPE}" == "rust" ]; then + command=" + set -e + cd /workspace/plugins/wasm-rust/extensions/${PLUGIN_NAME} + cargo build --target wasm32-wasi --release + cp target/wasm32-wasi/release/*.wasm plugin.wasm + tar czvf plugin.tar.gz plugin.wasm + echo ${{ secrets.REGISTRY_PASSWORD }} | oras login -u ${{ secrets.REGISTRY_USERNAME }} --password-stdin ${{ env.IMAGE_REGISTRY_SERVICE }} + oras push ${target_image} ${push_command} + oras push ${target_image_latest} ${push_command} + " + else + + command=" + echo "unkown type ${PLUGIN_TYPE}" + " + fi docker exec builder bash -c "$command" diff --git a/plugins/wasm-rust/DockerfileBuilder b/plugins/wasm-rust/DockerfileBuilder new file mode 100644 index 0000000000..3f9879af3b --- /dev/null +++ b/plugins/wasm-rust/DockerfileBuilder @@ -0,0 +1,38 @@ + +ARG RUST_VERSION +ARG ORAS_VERSION +ARG HIGRESS_VERSION + +ARG BASE_IMAGE=rust:${RUST_VERSION:-1.82} +FROM $BASE_IMAGE + +LABEL rust_version=$RUST_VERSION oras_version=$ORAS_VERSION + +RUN apt-get update \ + && apt-get install -y wget gcc gcc-multilib llvm clang \ + && rustup target add wasm32-wasi \ + && rm -rf /var/lib/apt/lists/* + +RUN arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \ + rust_version=${RUST_VERSION:-1.82}; \ + oras_version=${ORAS_VERSION:-1.0.0}; \ + higress_version=${HIGRESS_VERSION:-1.0.0-rc}; \ + echo "arch: '$arch'"; \ + echo "rust rust_version: '$rust_version'"; \ + echo "oras_version: '$oras_version'"; \ + echo "higress_version: '$higress_version'"; \ + case "$arch" in \ + 'amd64') \ + oras_url="https://github.com/oras-project/oras/releases/download/v$oras_version/oras_${oras_version}_linux_amd64.tar.gz"; \ + ;; \ + 'arm64') \ + oras_url="https://github.com/oras-project/oras/releases/download/v$oras_version/oras_${oras_version}_linux_arm64.tar.gz"; \ + ;; \ + *) echo >&2 "error: unsupported architecture '$arch' "; exit 1 ;; \ + esac; \ + echo "oras_url: '$oras_url'"; \ + wget -O oras.tgz "$oras_url" --progress=dot:giga; \ + tar -C /usr/local/bin -xzf oras.tgz && rm -rf oras.tgz; \ + echo "done"; + +ENV PATH=$PATH:/usr/local/bin diff --git a/plugins/wasm-rust/Makefile b/plugins/wasm-rust/Makefile index 5bc1809a38..66a547adf4 100644 --- a/plugins/wasm-rust/Makefile +++ b/plugins/wasm-rust/Makefile @@ -1,5 +1,10 @@ PLUGIN_NAME ?= say-hello +BUILDER_REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/ REGISTRY ?= higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/ +RUST_VERSION ?= 1.82 +ORAS_VERSION ?= 1.0.0 +HIGRESS_VERSION ?= 1.0.0-rc +BUILDER ?= ${BUILDER_REGISTRY}wasm-rust-builder:rust${RUST_VERSION}-oras${ORAS_VERSION} BUILD_TIME := $(shell date "+%Y%m%d-%H%M%S") COMMIT_ID := $(shell git rev-parse --short HEAD 2>/dev/null) IMAGE_TAG = $(if $(strip $(PLUGIN_VERSION)),${PLUGIN_VERSION},${BUILD_TIME}-${COMMIT_ID}) @@ -21,3 +26,16 @@ lint-base: lint: cargo fmt --all --check --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml cargo clippy --workspace --all-features --all-targets --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml + +builder: + docker buildx build --no-cache \ + --platform linux/amd64,linux/arm64 \ + --build-arg RUST_VERSION=$(RUST_VERSION) \ + --build-arg ORAS_VERSION=$(ORAS_VERSION) \ + --build-arg HIGRESS_VERSION=$(HIGRESS_VERSION) \ + -f DockerfileBuilder \ + -t ${BUILDER} \ + --push \ + . + @echo "" + @echo "image: ${BUILDER}" \ No newline at end of file From 8ad4970231c8da7c47ae5b5dcd9b8b7c8914a9f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Fri, 8 Nov 2024 10:14:02 +0800 Subject: [PATCH 50/64] update rust makefile (#1491) --- plugins/wasm-rust/Makefile | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/plugins/wasm-rust/Makefile b/plugins/wasm-rust/Makefile index 66a547adf4..2e04b2720f 100644 --- a/plugins/wasm-rust/Makefile +++ b/plugins/wasm-rust/Makefile @@ -28,14 +28,12 @@ lint: cargo clippy --workspace --all-features --all-targets --manifest-path extensions/${PLUGIN_NAME}/Cargo.toml builder: - docker buildx build --no-cache \ - --platform linux/amd64,linux/arm64 \ + DOCKER_BUILDKIT=1 docker build \ --build-arg RUST_VERSION=$(RUST_VERSION) \ --build-arg ORAS_VERSION=$(ORAS_VERSION) \ --build-arg HIGRESS_VERSION=$(HIGRESS_VERSION) \ -f DockerfileBuilder \ -t ${BUILDER} \ - --push \ . @echo "" - @echo "image: ${BUILDER}" \ No newline at end of file + @echo "image: ${BUILDER}" From d02c974af4f6bf89225993ec0b8474c268ce8206 Mon Sep 17 00:00:00 2001 From: Kent Dong Date: Fri, 8 Nov 2024 10:15:51 +0800 Subject: [PATCH 51/64] feat: Ensure all images are loaded to K8s before starting e2e tests (#1389) --- Makefile.core.mk | 4 ++++ test/e2e/conformance/base/eureka.yaml | 1 + test/e2e/conformance/base/opa.yaml | 2 ++ 3 files changed, 7 insertions(+) diff --git a/Makefile.core.mk b/Makefile.core.mk index b790d3c878..aa136c0fd6 100644 --- a/Makefile.core.mk +++ b/Makefile.core.mk @@ -288,6 +288,8 @@ delete-cluster: $(tools/kind) ## Delete kind cluster. .PHONY: kube-load-image kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster using the provided $IMAGE and $TAG. tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/higress $(TAG) + tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/pilot $(ISTIO_LATEST_IMAGE_TAG) + tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/gateway $(ENVOY_LATEST_IMAGE_TAG) tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/dubbo-provider-demo 0.0.3-x86 tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/nacos-standlone-rc3 1.0.0-RC3 tools/hack/docker-pull-image.sh docker.io/hashicorp/consul 1.16.0 @@ -298,6 +300,7 @@ kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster us tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server v1.0 tools/hack/docker-pull-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-body 1.0.0 tools/hack/docker-pull-image.sh openpolicyagent/opa latest + tools/hack/docker-pull-image.sh curlimages/curl latest tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin 1.0.2 tools/hack/docker-pull-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3 tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/dubbo-provider-demo 0.0.3-x86 @@ -310,6 +313,7 @@ kube-load-image: $(tools/kind) ## Install the Higress image to a kind cluster us tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-server v1.0 tools/hack/kind-load-image.sh higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/echo-body 1.0.0 tools/hack/kind-load-image.sh openpolicyagent/opa latest + tools/hack/kind-load-image.sh curlimages/curl latest tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/2456868764/httpbin 1.0.2 tools/hack/kind-load-image.sh registry.cn-hangzhou.aliyuncs.com/hinsteny/nacos-standlone-rc3 1.0.0-RC3 diff --git a/test/e2e/conformance/base/eureka.yaml b/test/e2e/conformance/base/eureka.yaml index 359c07b543..f2b96abdaa 100644 --- a/test/e2e/conformance/base/eureka.yaml +++ b/test/e2e/conformance/base/eureka.yaml @@ -66,6 +66,7 @@ spec: containers: - name: eureka image: bitinit/eureka + imagePullPolicy: IfNotPresent ports: - containerPort: 8761 name: http diff --git a/test/e2e/conformance/base/opa.yaml b/test/e2e/conformance/base/opa.yaml index 1b73341500..0348064e61 100644 --- a/test/e2e/conformance/base/opa.yaml +++ b/test/e2e/conformance/base/opa.yaml @@ -30,6 +30,7 @@ spec: containers: - name: opa image: openpolicyagent/opa:latest + imagePullPolicy: IfNotPresent ports: - containerPort: 8181 command: [ "opa", "run", "-s" ] @@ -57,6 +58,7 @@ spec: containers: - name: opa-test image: curlimages/curl:latest + imagePullPolicy: IfNotPresent command: - sh - -c From 0fbeb39cac399c07800fbcbf7fd504630eafee18 Mon Sep 17 00:00:00 2001 From: Ranjana761 <129291313+Ranjana761@users.noreply.github.com> Date: Fri, 8 Nov 2024 07:48:16 +0530 Subject: [PATCH 52/64] docs: Added back to top , contributors section and star history graph (#1440) --- README.md | 17 +++++++++++++++++ README_EN.md | 17 +++++++++++++++++ README_JP.md | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/README.md b/README.md index c93bf3b15e..9ae48a4577 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +

Higress
@@ -187,3 +188,19 @@ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start - Higress 控制台:https://github.com/higress-group/higress-console - Higress(独立运行版):https://github.com/higress-group/higress-standalone + +### 贡献者 + + + contributors + + +### Star History + +[![Star History](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date) + +

+ + ↑ 返回顶部 ↑ + +

diff --git a/README_EN.md b/README_EN.md index b099fa3e5b..1d91b5b420 100644 --- a/README_EN.md +++ b/README_EN.md @@ -1,3 +1,4 @@ +

Higress
@@ -87,3 +88,19 @@ Higress would not be possible without the valuable open-source work of projects - Higress Console: https://github.com/higress-group/higress-console - Higress Standalone: https://github.com/higress-group/higress-standalone + +### Contributors + + + contributors + + +### Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date) + +

+ + ↑ Back to Top ↑ + +

\ No newline at end of file diff --git a/README_JP.md b/README_JP.md index 8573576ee0..58a626f4d2 100644 --- a/README_JP.md +++ b/README_JP.md @@ -1,3 +1,4 @@ +

Higress
@@ -187,3 +188,19 @@ WeChat公式アカウント: - Higressコンソール:https://github.com/higress-group/higress-console - Higress(スタンドアロン版):https://github.com/higress-group/higress-standalone + +### 貢献者 + + + contributors + + +### スターの歴史 + +[![スターの歴史チャート](https://api.star-history.com/svg?repos=alibaba/higress&type=Date)](https://star-history.com/#alibaba/higress&Date) + +

+ + ↑ トップに戻る ↑ + +

\ No newline at end of file From 7bd438877b8ce6db907536e2df4a04d2030bd06e Mon Sep 17 00:00:00 2001 From: Squidward <56287847+Chi-Kai@users.noreply.github.com> Date: Fri, 8 Nov 2024 13:48:32 +0800 Subject: [PATCH 53/64] add textin embedding for ai-cache (#1493) --- .../extensions/ai-cache/embedding/provider.go | 17 +- .../extensions/ai-cache/embedding/textin.go | 161 ++++++++++++++++++ 2 files changed, 175 insertions(+), 3 deletions(-) create mode 100644 plugins/wasm-go/extensions/ai-cache/embedding/textin.go diff --git a/plugins/wasm-go/extensions/ai-cache/embedding/provider.go b/plugins/wasm-go/extensions/ai-cache/embedding/provider.go index 909edf129c..28dc2cb794 100644 --- a/plugins/wasm-go/extensions/ai-cache/embedding/provider.go +++ b/plugins/wasm-go/extensions/ai-cache/embedding/provider.go @@ -9,6 +9,7 @@ import ( const ( PROVIDER_TYPE_DASHSCOPE = "dashscope" + PROVIDER_TYPE_TEXTIN = "textin" ) type providerInitializer interface { @@ -19,6 +20,7 @@ type providerInitializer interface { var ( providerInitializers = map[string]providerInitializer{ PROVIDER_TYPE_DASHSCOPE: &dashScopeProviderInitializer{}, + PROVIDER_TYPE_TEXTIN: &textInProviderInitializer{}, } ) @@ -38,6 +40,15 @@ type ProviderConfig struct { // @Title zh-CN 文本特征提取服务 API Key // @Description zh-CN 文本特征提取服务 API Key apiKey string + //@Title zh-CN TextIn x-ti-app-id + // @Description zh-CN 仅适用于 TextIn 服务。参考 https://www.textin.com/document/acge_text_embedding + textinAppId string + //@Title zh-CN TextIn x-ti-secret-code + // @Description zh-CN 仅适用于 TextIn 服务。参考 https://www.textin.com/document/acge_text_embedding + textinSecretCode string + //@Title zh-CN TextIn request matryoshka_dim + // @Description zh-CN 仅适用于 TextIn 服务, 指定返回的向量维度。参考 https://www.textin.com/document/acge_text_embedding + textinMatryoshkaDim int // @Title zh-CN 文本特征提取服务超时时间 // @Description zh-CN 文本特征提取服务超时时间 timeout uint32 @@ -52,6 +63,9 @@ func (c *ProviderConfig) FromJson(json gjson.Result) { c.serviceHost = json.Get("serviceHost").String() c.servicePort = json.Get("servicePort").Int() c.apiKey = json.Get("apiKey").String() + c.textinAppId = json.Get("textinAppId").String() + c.textinSecretCode = json.Get("textinSecretCode").String() + c.textinMatryoshkaDim = int(json.Get("textinMatryoshkaDim").Int()) c.timeout = uint32(json.Get("timeout").Int()) c.model = json.Get("model").String() if c.timeout == 0 { @@ -63,9 +77,6 @@ func (c *ProviderConfig) Validate() error { if c.serviceName == "" { return errors.New("embedding service name is required") } - if c.apiKey == "" { - return errors.New("embedding service API key is required") - } if c.typ == "" { return errors.New("embedding service type is required") } diff --git a/plugins/wasm-go/extensions/ai-cache/embedding/textin.go b/plugins/wasm-go/extensions/ai-cache/embedding/textin.go new file mode 100644 index 0000000000..9bc474041c --- /dev/null +++ b/plugins/wasm-go/extensions/ai-cache/embedding/textin.go @@ -0,0 +1,161 @@ +package embedding + +import ( + "encoding/json" + "errors" + "fmt" + "net/http" + "strconv" + + "github.com/alibaba/higress/plugins/wasm-go/pkg/wrapper" +) + +const ( + TEXTIN_DOMAIN = "api.textin.com" + TEXTIN_PORT = 443 + TEXTIN_DEFAULT_MODEL_NAME = "acge-text-embedding" + TEXTIN_ENDPOINT = "/ai/service/v1/acge_embedding" +) + +type textInProviderInitializer struct { +} + +func (t *textInProviderInitializer) ValidateConfig(config ProviderConfig) error { + if config.textinAppId == "" { + return errors.New("embedding service TextIn App ID is required") + } + if config.textinSecretCode == "" { + return errors.New("embedding service TextIn Secret Code is required") + } + if config.textinMatryoshkaDim == 0 { + return errors.New("embedding service TextIn Matryoshka Dim is required") + } + return nil +} + +func (t *textInProviderInitializer) CreateProvider(c ProviderConfig) (Provider, error) { + if c.servicePort == 0 { + c.servicePort = TEXTIN_PORT + } + if c.serviceHost == "" { + c.serviceHost = TEXTIN_DOMAIN + } + return &TIProvider{ + config: c, + client: wrapper.NewClusterClient(wrapper.FQDNCluster{ + FQDN: c.serviceName, + Host: c.serviceHost, + Port: int64(c.servicePort), + }), + }, nil +} + +func (t *TIProvider) GetProviderType() string { + return PROVIDER_TYPE_TEXTIN +} + +type TextInResponse struct { + Code int `json:"code"` + Message string `json:"message"` + Duration float64 `json:"duration"` + Result TextInResult `json:"result"` +} + +type TextInResult struct { + Embeddings [][]float64 `json:"embedding"` + MatryoshkaDim int `json:"matryoshka_dim"` +} + +type TextInEmbeddingRequest struct { + Input []string `json:"input"` + MatryoshkaDim int `json:"matryoshka_dim"` +} + +type TIProvider struct { + config ProviderConfig + client wrapper.HttpClient +} + +func (t *TIProvider) constructParameters(texts []string, log wrapper.Log) (string, [][2]string, []byte, error) { + + data := TextInEmbeddingRequest{ + Input: texts, + MatryoshkaDim: t.config.textinMatryoshkaDim, + } + + requestBody, err := json.Marshal(data) + if err != nil { + log.Errorf("failed to marshal request data: %v", err) + return "", nil, nil, err + } + + if t.config.textinAppId == "" { + err := errors.New("textinAppId is empty") + log.Errorf("failed to construct headers: %v", err) + return "", nil, nil, err + } + if t.config.textinSecretCode == "" { + err := errors.New("textinSecretCode is empty") + log.Errorf("failed to construct headers: %v", err) + return "", nil, nil, err + } + + headers := [][2]string{ + {"x-ti-app-id", t.config.textinAppId}, + {"x-ti-secret-code", t.config.textinSecretCode}, + {"Content-Type", "application/json"}, + } + + return TEXTIN_ENDPOINT, headers, requestBody, err +} + +func (t *TIProvider) parseTextEmbedding(responseBody []byte) (*TextInResponse, error) { + var resp TextInResponse + err := json.Unmarshal(responseBody, &resp) + if err != nil { + return nil, err + } + return &resp, nil +} + +func (t *TIProvider) GetEmbedding( + queryString string, + ctx wrapper.HttpContext, + log wrapper.Log, + callback func(emb []float64, err error)) error { + embUrl, embHeaders, embRequestBody, err := t.constructParameters([]string{queryString}, log) + if err != nil { + log.Errorf("failed to construct parameters: %v", err) + return err + } + + var resp *TextInResponse + err = t.client.Post(embUrl, embHeaders, embRequestBody, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + + if statusCode != http.StatusOK { + err = errors.New("failed to get embedding due to status code: " + strconv.Itoa(statusCode)) + callback(nil, err) + return + } + + log.Debugf("get embedding response: %d, %s", statusCode, responseBody) + + resp, err = t.parseTextEmbedding(responseBody) + if err != nil { + err = fmt.Errorf("failed to parse response: %v", err) + callback(nil, err) + return + } + + if len(resp.Result.Embeddings) == 0 { + err = errors.New("no embedding found in response") + callback(nil, err) + return + } + + callback(resp.Result.Embeddings[0], nil) + + }, t.config.timeout) + return err +} From 36607155062a8582708ed26b9dc869b209dbb1fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Fri, 8 Nov 2024 13:59:12 +0800 Subject: [PATCH 54/64] release 2.0.3 (#1494) --- .github/workflows/release-crd.yaml | 24 ++++++++++++++++++++++++ VERSION | 2 +- helm/core/crds/istio-envoyfilter.yaml | 1 + helm/higress/Chart.yaml | 8 ++++---- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/release-crd.yaml diff --git a/.github/workflows/release-crd.yaml b/.github/workflows/release-crd.yaml new file mode 100644 index 0000000000..df0f28ae80 --- /dev/null +++ b/.github/workflows/release-crd.yaml @@ -0,0 +1,24 @@ +name: Release CRD to GitHub + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: ~ + +jobs: + release-crd: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: generate crds + run: | + cat helm/core/crds/customresourcedefinitions.gen.yaml helm/core/crds/istio-envoyfilter.yaml > crd.yaml + + - name: Upload hgctl packages to the GitHub release + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') + with: + files: | + crd.yaml diff --git a/VERSION b/VERSION index f3b15f3f8e..f256be6034 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.2 +v2.0.3 diff --git a/helm/core/crds/istio-envoyfilter.yaml b/helm/core/crds/istio-envoyfilter.yaml index 9fab238003..e22b2c6940 100644 --- a/helm/core/crds/istio-envoyfilter.yaml +++ b/helm/core/crds/istio-envoyfilter.yaml @@ -1,3 +1,4 @@ +--- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition metadata: diff --git a/helm/higress/Chart.yaml b/helm/higress/Chart.yaml index 51fe7f7de2..437a0f6a92 100644 --- a/helm/higress/Chart.yaml +++ b/helm/higress/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.0.2 +appVersion: 2.0.3 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.2 + version: 2.0.3 - name: higress-console repository: "https://higress.io/helm-charts/" - version: 1.4.4 + version: 1.4.5 type: application -version: 2.0.2 +version: 2.0.3 From 7697af9d2bee4e19c8909e9e3eb598f66407f01e Mon Sep 17 00:00:00 2001 From: johnlanni Date: Fri, 8 Nov 2024 14:07:12 +0800 Subject: [PATCH 55/64] update chart.lock --- helm/core/Chart.yaml | 4 ++-- helm/higress/Chart.lock | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/helm/core/Chart.yaml b/helm/core/Chart.yaml index 04d287a95f..347267ee56 100644 --- a/helm/core/Chart.yaml +++ b/helm/core/Chart.yaml @@ -1,5 +1,5 @@ apiVersion: v2 -appVersion: 2.0.2 +appVersion: 2.0.3 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.2 +version: 2.0.3 diff --git a/helm/higress/Chart.lock b/helm/higress/Chart.lock index 9c71c9f1c8..5d19538bda 100644 --- a/helm/higress/Chart.lock +++ b/helm/higress/Chart.lock @@ -1,9 +1,9 @@ dependencies: - name: higress-core repository: file://../core - version: 2.0.2 + version: 2.0.3 - name: higress-console repository: https://higress.io/helm-charts/ - version: 1.4.4 -digest: sha256:a424449caa01a71798c7fec9769ef97be7658354c028a3cede4790e4b6094532 -generated: "2024-10-28T18:50:27.528097+08:00" + version: 1.4.5 +digest: sha256:74b772113264168483961f5d0424459fd7359adc509a4b50400229581d7cddbf +generated: "2024-11-08T14:06:51.871719+08:00" From 39b6eac9d0184254218c05fc082b5f1c19b62d50 Mon Sep 17 00:00:00 2001 From: xingyunyang01 <94745901+xingyunyang01@users.noreply.github.com> Date: Mon, 11 Nov 2024 11:11:02 +0800 Subject: [PATCH 56/64] AI Agent plugin adds JSON formatting output feature (#1374) --- plugins/wasm-go/extensions/ai-agent/README.md | 90 ++++++- .../wasm-go/extensions/ai-agent/README_EN.md | 76 +++++- plugins/wasm-go/extensions/ai-agent/config.go | 22 ++ plugins/wasm-go/extensions/ai-agent/main.go | 230 +++++++++++++----- .../extensions/ai-agent/promptTpl/prompt.go | 4 + 5 files changed, 355 insertions(+), 67 deletions(-) diff --git a/plugins/wasm-go/extensions/ai-agent/README.md b/plugins/wasm-go/extensions/ai-agent/README.md index 4869365b59..002d8356d1 100644 --- a/plugins/wasm-go/extensions/ai-agent/README.md +++ b/plugins/wasm-go/extensions/ai-agent/README.md @@ -5,7 +5,7 @@ description: AI Agent插件配置参考 --- ## 功能说明 -一个可定制化的 API AI Agent,支持配置 http method 类型为 GET 与 POST 的 API,支持多轮对话,支持流式与非流式模式。 +一个可定制化的 API AI Agent,支持配置 http method 类型为 GET 与 POST 的 API,支持多轮对话,支持流式与非流式模式,支持将结果格式化为自定义的 json。 agent流程图如下: ![ai-agent](https://img.alicdn.com/imgextra/i1/O1CN01PGSDW31WQfEPm173u_!!6000000002783-0-tps-2733-1473.jpg) @@ -21,6 +21,7 @@ agent流程图如下: | `llm` | object | 必填 | - | 配置 AI 服务提供商的信息 | | `apis` | object | 必填 | - | 配置外部 API 服务提供商的信息 | | `promptTemplate` | object | 非必填 | - | 配置 Agent ReAct 模板的信息 | +| `jsonResp` | object | 非必填 | - | 配置 json 格式化的相关信息 | `llm`的配置字段说明如下: @@ -78,7 +79,14 @@ agent流程图如下: | `observation` | string | 非必填 | - | Agent ReAct 模板的 observation 部分 | | `thought2` | string | 非必填 | - | Agent ReAct 模板的 thought2 部分 | -## 用法示例 +`jsonResp`的配置字段说明如下: + +| 名称 | 数据类型 | 填写要求 | 默认值 | 描述 | +|--------------------|-----------|---------|--------|-----------------------------------| +| `enable` | bool | 非必填 | false | 是否开启 json 格式化。 | +| `jsonSchema` | string | 非必填 | - | 自定义 json schema | + +## 用法示例-不开启 json 格式化 **配置信息** @@ -293,7 +301,7 @@ deepl提供了一个工具,用于翻译给定的句子,支持多语言。。 **请求示例** ```shell -curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ +curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \ -H 'Accept: application/json, text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"我想在济南市鑫盛大厦附近喝咖啡,给我推荐几个"}],"presence_penalty":0,"temperature":0,"top_p":0}' @@ -308,7 +316,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ **请求示例** ```shell -curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ +curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \ -H 'Accept: application/json, text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"济南市现在的天气情况如何?"}],"presence_penalty":0,"temperature":0,"top_p":0}' @@ -323,7 +331,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ **请求示例** ```shell -curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ +curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \ -H 'Accept: application/json, text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role": "user","content": "济南的天气如何?"},{ "role": "assistant","content": "目前,济南市的天气为多云,气温为24℃,数据更新时间为2024年9月12日21时50分14秒。"},{"role": "user","content": "北京呢?"}],"presence_penalty":0,"temperature":0,"top_p":0}' @@ -338,7 +346,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ **请求示例** ```shell -curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ +curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \ -H 'Accept: application/json, text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"济南市现在的天气情况如何?用华氏度表示,用日语回答"}],"presence_penalty":0,"temperature":0,"top_p":0}' @@ -353,7 +361,7 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ **请求示例** ```shell -curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ +curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \ -H 'Accept: application/json, text/event-stream' \ -H 'Content-Type: application/json' \ --data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"帮我用德语翻译以下句子:九头蛇万岁!"}],"presence_penalty":0,"temperature":0,"top_p":0}' @@ -364,3 +372,71 @@ curl 'http://<这里换成网关公网IP>/api/openai/v1/chat/completions' \ ```json {"id":"65dcf12c-61ff-9e68-bffa-44fc9e6070d5","choices":[{"index":0,"message":{"role":"assistant","content":" “九头蛇万岁!”的德语翻译为“Hoch lebe Hydra!”。"},"finish_reason":"stop"}],"created":1724043865,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":908,"completion_tokens":52,"total_tokens":960}} ``` + +## 用法示例-开启 json 格式化 + +**配置信息** +在上述配置的基础上增加 jsonResp 配置 +```yaml +jsonResp: + enable: true +``` + +**请求示例** + +```shell +curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \ +-H 'Accept: application/json, text/event-stream' \ +-H 'Content-Type: application/json' \ +--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"北京市现在的天气情况如何?"}],"presence_penalty":0,"temperature":0,"top_p":0}' +``` + +**响应示例** + +```json +{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"city\": \"北京市\", \"weather_condition\": \"多云\", \"temperature\": \"19℃\", \"data_update_time\": \"2024年10月9日16时37分53秒\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}} +``` +如果不自定义 json schema,大模型会自动生成一个 json 格式 + +**配置信息** +增加自定义 json schema 配置 +```yaml +jsonResp: + enable: true + jsonSchema: | + title: WeatherSchema + type: object + properties: + location: + type: string + description: 城市名称. + weather: + type: string + description: 天气情况. + temperature: + type: string + description: 温度. + update_time: + type: string + description: 数据更新时间. + required: + - location + - weather + - temperature + additionalProperties: false +``` + +**请求示例** + +```shell +curl 'http://<这里换成网关地址>/api/openai/v1/chat/completions' \ +-H 'Accept: application/json, text/event-stream' \ +-H 'Content-Type: application/json' \ +--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"北京市现在的天气情况如何?"}],"presence_penalty":0,"temperature":0,"top_p":0}' +``` + +**响应示例** + +```json +{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"location\": \"北京市\", \"weather\": \"多云\", \"temperature\": \"19℃\", \"update_time\": \"2024年10月9日16时37分53秒\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}} +``` \ No newline at end of file diff --git a/plugins/wasm-go/extensions/ai-agent/README_EN.md b/plugins/wasm-go/extensions/ai-agent/README_EN.md index ae2b596556..94829932f2 100644 --- a/plugins/wasm-go/extensions/ai-agent/README_EN.md +++ b/plugins/wasm-go/extensions/ai-agent/README_EN.md @@ -4,7 +4,7 @@ keywords: [ AI Gateway, AI Agent ] description: AI Agent plugin configuration reference --- ## Functional Description -A customizable API AI Agent that supports configuring HTTP method types as GET and POST APIs. Supports multiple dialogue rounds, streaming and non-streaming modes. +A customizable API AI Agent that supports configuring HTTP method types as GET and POST APIs. Supports multiple dialogue rounds, streaming and non-streaming modes, support for formatting results as custom json. The agent flow chart is as follows: ![ai-agent](https://github.com/user-attachments/assets/b0761a0c-1afa-496c-a98e-bb9f38b340f8) @@ -20,6 +20,7 @@ Plugin execution priority: `200` | `llm` | object | Required | - | Configuration information for AI service provider | | `apis` | object | Required | - | Configuration information for external API service provider | | `promptTemplate` | object | Optional | - | Configuration information for Agent ReAct template | +| `jsonResp` | object | Optional | - | Configuring json formatting information | The configuration fields for `llm` are as follows: | Name | Data Type | Requirement | Default Value | Description | @@ -71,7 +72,13 @@ The configuration fields for `chTemplate` and `enTemplate` are as follows: | `observation` | string | Optional | - | The observation part of the Agent ReAct template | | `thought2` | string | Optional | - | The thought2 part of the Agent ReAct template | -## Usage Example +The configuration fields for `jsonResp` are as follows: +| Name | Data Type | Requirement | Default Value | Description | +|--------------------|-----------|-------------|---------------|------------------------------------| +| `enable` | bool | Optional | - | Whether to enable json formatting. | +| `jsonSchema` | string | Optional | - | Custom json schema | + +## Usage Example-disable json formatting **Configuration Information** ```yaml llm: @@ -335,3 +342,68 @@ curl 'http:///api/openai/v1/chat/completions' \ {"id":"65dcf12c-61ff-9e68-bffa-44fc9e6070d5","choices":[{"index":0,"message":{"role":"assistant","content":" The German translation of \"Hail Hydra!\" is \"Hoch lebe Hydra!\"."},"finish_reason":"stop"}],"created":1724043865,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":908,"completion_tokens":52,"total_tokens":960}} ``` +## Usage Example-enable json formatting +**Configuration Information** +Add jsonResp configuration to the above configuration +```yaml +jsonResp: + enable: true +``` + +**Request Example** +```shell +curl 'http:///api/openai/v1/chat/completions' \ +-H 'Accept: application/json, text/event-stream' \ +-H 'Content-Type: application/json' \ +--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"What is the current weather in Beijing ?"}],"presence_penalty":0,"temperature":0,"top_p":0}' +``` + +**Response Example** + +```json +{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"city\": \"BeiJing\", \"weather_condition\": \"cloudy\", \"temperature\": \"19℃\", \"data_update_time\": \"Oct 9, 2024, at 16:37\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}} +``` +If you don't customise the json schema, the big model will automatically generate a json format + +**Configuration Information** +Add custom json schema configuration +```yaml +jsonResp: + enable: true + jsonSchema: + title: WeatherSchema + type: object + properties: + location: + type: string + description: city name. + weather: + type: string + description: weather conditions. + temperature: + type: string + description: temperature. + update_time: + type: string + description: the update time of data. + required: + - location + - weather + - temperature + additionalProperties: false +``` + +**Request Example** + +```shell +curl 'http:///api/openai/v1/chat/completions' \ +-H 'Accept: application/json, text/event-stream' \ +-H 'Content-Type: application/json' \ +--data-raw '{"model":"qwen","frequency_penalty":0,"max_tokens":800,"stream":false,"messages":[{"role":"user","content":"What is the current weather in Beijing ?"}],"presence_penalty":0,"temperature":0,"top_p":0}' +``` + +**Response Example** + +```json +{"id":"ebd6ea91-8e38-9e14-9a5b-90178d2edea4","choices":[{"index":0,"message":{"role":"assistant","content": "{\"location\": \"Beijing\", \"weather\": \"cloudy\", \"temperature\": \"19℃\", \"update_time\": \"Oct 9, 2024, at 16:37\"}"},"finish_reason":"stop"}],"created":1723187991,"model":"qwen-max-0403","object":"chat.completion","usage":{"prompt_tokens":890,"completion_tokens":56,"total_tokens":946}} +``` \ No newline at end of file diff --git a/plugins/wasm-go/extensions/ai-agent/config.go b/plugins/wasm-go/extensions/ai-agent/config.go index ae694a42eb..7c078d22cb 100644 --- a/plugins/wasm-go/extensions/ai-agent/config.go +++ b/plugins/wasm-go/extensions/ai-agent/config.go @@ -211,6 +211,15 @@ type LLMInfo struct { MaxTokens int64 `yaml:"maxToken" json:"maxTokens"` } +type JsonResp struct { + // @Title zh-CN Enable + // @Description zh-CN 是否要启用json格式化输出 + Enable bool `yaml:"enable" json:"enable"` + // @Title zh-CN Json Schema + // @Description zh-CN 用以验证响应json的Json Schema, 为空则只验证返回的响应是否为合法json + JsonSchema map[string]interface{} `required:"false" json:"jsonSchema" yaml:"jsonSchema"` +} + type PluginConfig struct { // @Title zh-CN 返回 HTTP 响应的模版 // @Description zh-CN 用 %s 标记需要被 cache value 替换的部分 @@ -225,6 +234,7 @@ type PluginConfig struct { LLMClient wrapper.HttpClient `yaml:"-" json:"-"` APIsParam []APIsParam `yaml:"-" json:"-"` PromptTemplate PromptTemplate `yaml:"promptTemplate" json:"promptTemplate"` + JsonResp JsonResp `yaml:"jsonResp" json:"jsonResp"` } func initResponsePromptTpl(gjson gjson.Result, c *PluginConfig) { @@ -402,3 +412,15 @@ func initLLMClient(gjson gjson.Result, c *PluginConfig) { Host: c.LLMInfo.Domain, }) } + +func initJsonResp(gjson gjson.Result, c *PluginConfig) { + c.JsonResp.Enable = false + if c.JsonResp.Enable = gjson.Get("jsonResp.enable").Bool(); c.JsonResp.Enable { + c.JsonResp.JsonSchema = nil + if jsonSchemaValue := gjson.Get("jsonResp.jsonSchema"); jsonSchemaValue.Exists() { + if schemaValue, ok := jsonSchemaValue.Value().(map[string]interface{}); ok { + c.JsonResp.JsonSchema = schemaValue + } + } + } +} diff --git a/plugins/wasm-go/extensions/ai-agent/main.go b/plugins/wasm-go/extensions/ai-agent/main.go index 97acb8e183..5debf0deab 100644 --- a/plugins/wasm-go/extensions/ai-agent/main.go +++ b/plugins/wasm-go/extensions/ai-agent/main.go @@ -2,8 +2,10 @@ package main import ( "encoding/json" + "errors" "fmt" "net/http" + "net/url" "regexp" "strings" @@ -47,6 +49,8 @@ func parseConfig(gjson gjson.Result, c *PluginConfig, log wrapper.Log) error { initLLMClient(gjson, c) + initJsonResp(gjson, c) + return nil } @@ -76,10 +80,10 @@ func firstReq(ctx wrapper.HttpContext, config PluginConfig, prompt string, rawRe log.Debugf("[onHttpRequestBody] newRequestBody: %s", string(newbody)) err := proxywasm.ReplaceHttpRequestBody(newbody) if err != nil { - log.Debug("替换失败") + log.Debugf("failed replace err: %s", err.Error()) proxywasm.SendHttpResponse(200, [][2]string{{"content-type", "application/json; charset=utf-8"}}, []byte(fmt.Sprintf(config.ReturnResponseTemplate, "替换失败"+err.Error())), -1) } - log.Debug("[onHttpRequestBody] request替换成功") + log.Debug("[onHttpRequestBody] replace request success") return types.ActionContinue } } @@ -175,11 +179,103 @@ func onHttpResponseHeaders(ctx wrapper.HttpContext, config PluginConfig, log wra return types.ActionContinue } -func toolsCallResult(ctx wrapper.HttpContext, config PluginConfig, content string, rawResponse Response, log wrapper.Log, statusCode int, responseBody []byte) { +func extractJson(bodyStr string) (string, error) { + // simply extract json from response body string + startIndex := strings.Index(bodyStr, "{") + endIndex := strings.LastIndex(bodyStr, "}") + 1 + + // if not found + if startIndex == -1 || startIndex >= endIndex { + return "", errors.New("cannot find json in the response body") + } + + jsonStr := bodyStr[startIndex:endIndex] + + // attempt to parse the JSON + var result map[string]interface{} + err := json.Unmarshal([]byte(jsonStr), &result) + if err != nil { + return "", err + } + return jsonStr, nil +} + +func jsonFormat(llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonSchema map[string]interface{}, assistantMessage Message, actionInput string, headers [][2]string, streamMode bool, rawResponse Response, log wrapper.Log) string { + prompt := fmt.Sprintf(prompttpl.Json_Resp_Template, jsonSchema, actionInput) + + messages := []dashscope.Message{{Role: "user", Content: prompt}} + + completion := dashscope.Completion{ + Model: llmInfo.Model, + Messages: messages, + } + + completionSerialized, _ := json.Marshal(completion) + var content string + err := llmClient.Post( + llmInfo.Path, + headers, + completionSerialized, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + //得到gpt的返回结果 + var responseCompletion dashscope.CompletionResponse + _ = json.Unmarshal(responseBody, &responseCompletion) + log.Infof("[jsonFormat] content: %s", responseCompletion.Choices[0].Message.Content) + content = responseCompletion.Choices[0].Message.Content + jsonStr, err := extractJson(content) + if err != nil { + log.Debugf("[onHttpRequestBody] extractJson err: %s", err.Error()) + jsonStr = content + } + + if streamMode { + stream(jsonStr, rawResponse, log) + } else { + noneStream(assistantMessage, jsonStr, rawResponse, log) + } + }, uint32(llmInfo.MaxExecutionTime)) + if err != nil { + log.Debugf("[onHttpRequestBody] completion err: %s", err.Error()) + proxywasm.ResumeHttpRequest() + } + return content +} + +func noneStream(assistantMessage Message, actionInput string, rawResponse Response, log wrapper.Log) { + assistantMessage.Role = "assistant" + assistantMessage.Content = actionInput + rawResponse.Choices[0].Message = assistantMessage + newbody, err := json.Marshal(rawResponse) + if err != nil { + proxywasm.ResumeHttpResponse() + return + } else { + proxywasm.ReplaceHttpResponseBody(newbody) + + log.Debug("[onHttpResponseBody] replace response success") + proxywasm.ResumeHttpResponse() + } +} + +func stream(actionInput string, rawResponse Response, log wrapper.Log) { + headers := [][2]string{{"content-type", "text/event-stream; charset=utf-8"}} + proxywasm.ReplaceHttpResponseHeaders(headers) + // Remove quotes from actionInput + actionInput = strings.Trim(actionInput, "\"") + returnStreamResponseTemplate := `data:{"id":"%s","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"%s","object":"chat.completion","usage":{"prompt_tokens":%d,"completion_tokens":%d,"total_tokens":%d}}` + "\n\ndata:[DONE]\n\n" + newbody := fmt.Sprintf(returnStreamResponseTemplate, rawResponse.ID, actionInput, rawResponse.Model, rawResponse.Usage.PromptTokens, rawResponse.Usage.CompletionTokens, rawResponse.Usage.TotalTokens) + log.Infof("[onHttpResponseBody] newResponseBody: ", newbody) + proxywasm.ReplaceHttpResponseBody([]byte(newbody)) + + log.Debug("[onHttpResponseBody] replace response success") + proxywasm.ResumeHttpResponse() +} + +func toolsCallResult(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonResp JsonResp, aPIsParam []APIsParam, aPIClient []wrapper.HttpClient, content string, rawResponse Response, log wrapper.Log, statusCode int, responseBody []byte) { if statusCode != http.StatusOK { log.Debugf("statusCode: %d", statusCode) } - log.Info("========函数返回结果========") + log.Info("========function result========") log.Infof(string(responseBody)) observation := "Observation: " + string(responseBody) @@ -187,15 +283,15 @@ func toolsCallResult(ctx wrapper.HttpContext, config PluginConfig, content strin dashscope.MessageStore.AddForUser(observation) completion := dashscope.Completion{ - Model: config.LLMInfo.Model, + Model: llmInfo.Model, Messages: dashscope.MessageStore, - MaxTokens: config.LLMInfo.MaxTokens, + MaxTokens: llmInfo.MaxTokens, } - headers := [][2]string{{"Content-Type", "application/json"}, {"Authorization", "Bearer " + config.LLMInfo.APIKey}} + headers := [][2]string{{"Content-Type", "application/json"}, {"Authorization", "Bearer " + llmInfo.APIKey}} completionSerialized, _ := json.Marshal(completion) - err := config.LLMClient.Post( - config.LLMInfo.Path, + err := llmClient.Post( + llmInfo.Path, headers, completionSerialized, func(statusCode int, responseHeaders http.Header, responseBody []byte) { @@ -205,42 +301,31 @@ func toolsCallResult(ctx wrapper.HttpContext, config PluginConfig, content strin log.Infof("[toolsCall] content: %s", responseCompletion.Choices[0].Message.Content) if responseCompletion.Choices[0].Message.Content != "" { - retType, actionInput := toolsCall(ctx, config, responseCompletion.Choices[0].Message.Content, rawResponse, log) + retType, actionInput := toolsCall(ctx, llmClient, llmInfo, jsonResp, aPIsParam, aPIClient, responseCompletion.Choices[0].Message.Content, rawResponse, log) if retType == types.ActionContinue { //得到了Final Answer var assistantMessage Message + var streamMode bool if ctx.GetContext(StreamContextKey) == nil { - assistantMessage.Role = "assistant" - assistantMessage.Content = actionInput - rawResponse.Choices[0].Message = assistantMessage - newbody, err := json.Marshal(rawResponse) - if err != nil { - proxywasm.ResumeHttpResponse() - return + streamMode = false + if jsonResp.Enable { + jsonFormat(llmClient, llmInfo, jsonResp.JsonSchema, assistantMessage, actionInput, headers, streamMode, rawResponse, log) } else { - proxywasm.ReplaceHttpResponseBody(newbody) - - log.Debug("[onHttpResponseBody] response替换成功") - proxywasm.ResumeHttpResponse() + noneStream(assistantMessage, actionInput, rawResponse, log) } } else { - headers := [][2]string{{"content-type", "text/event-stream; charset=utf-8"}} - proxywasm.ReplaceHttpResponseHeaders(headers) - // Remove quotes from actionInput - actionInput = strings.Trim(actionInput, "\"") - returnStreamResponseTemplate := `data:{"id":"%s","choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"finish_reason":"stop"}],"model":"%s","object":"chat.completion","usage":{"prompt_tokens":%d,"completion_tokens":%d,"total_tokens":%d}}` + "\n\ndata:[DONE]\n\n" - newbody := fmt.Sprintf(returnStreamResponseTemplate, rawResponse.ID, actionInput, rawResponse.Model, rawResponse.Usage.PromptTokens, rawResponse.Usage.CompletionTokens, rawResponse.Usage.TotalTokens) - log.Infof("[onHttpResponseBody] newResponseBody: ", newbody) - proxywasm.ReplaceHttpResponseBody([]byte(newbody)) - - log.Debug("[onHttpResponseBody] response替换成功") - proxywasm.ResumeHttpResponse() + streamMode = true + if jsonResp.Enable { + jsonFormat(llmClient, llmInfo, jsonResp.JsonSchema, assistantMessage, actionInput, headers, streamMode, rawResponse, log) + } else { + stream(actionInput, rawResponse, log) + } } } } else { proxywasm.ResumeHttpRequest() } - }, uint32(config.LLMInfo.MaxExecutionTime)) + }, uint32(llmInfo.MaxExecutionTime)) if err != nil { log.Debugf("[onHttpRequestBody] completion err: %s", err.Error()) proxywasm.ResumeHttpRequest() @@ -294,7 +379,7 @@ func outputParser(response string, log wrapper.Log) (string, string) { return "", "" } -func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, rawResponse Response, log wrapper.Log) (types.Action, string) { +func toolsCall(ctx wrapper.HttpContext, llmClient wrapper.HttpClient, llmInfo LLMInfo, jsonResp JsonResp, aPIsParam []APIsParam, aPIClient []wrapper.HttpClient, content string, rawResponse Response, log wrapper.Log) (types.Action, string) { dashscope.MessageStore.AddForAssistant(content) action, actionInput := outputParser(content, log) @@ -305,9 +390,9 @@ func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, raw } count := ctx.GetContext(ToolCallsCount).(int) count++ - log.Debugf("toolCallsCount:%d, config.LLMInfo.MaxIterations=%d", count, config.LLMInfo.MaxIterations) + log.Debugf("toolCallsCount:%d, config.LLMInfo.MaxIterations=%d", count, llmInfo.MaxIterations) //函数递归调用次数,达到了预设的循环次数,强制结束 - if int64(count) > config.LLMInfo.MaxIterations { + if int64(count) > llmInfo.MaxIterations { ctx.SetContext(ToolCallsCount, 0) return types.ActionContinue, "" } else { @@ -316,15 +401,14 @@ func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, raw //没得到最终答案 - var url string + var urlStr string var headers [][2]string var apiClient wrapper.HttpClient var method string var reqBody []byte - var key string var maxExecutionTime int64 - for i, apisParam := range config.APIsParam { + for i, apisParam := range aPIsParam { maxExecutionTime = apisParam.MaxExecutionTime for _, tools_param := range apisParam.ToolsParam { if action == tools_param.ToolName { @@ -340,28 +424,37 @@ func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, raw method = tools_param.Method - // 组装 headers 和 key - headers = [][2]string{{"Content-Type", "application/json"}} - if apisParam.APIKey.Name != "" { - if apisParam.APIKey.In == "query" { - key = "?" + apisParam.APIKey.Name + "=" + apisParam.APIKey.Value - } else if apisParam.APIKey.In == "header" { - headers = append(headers, [2]string{"Authorization", apisParam.APIKey.Name + " " + apisParam.APIKey.Value}) + // 组装 URL 和请求体 + urlStr = apisParam.URL + tools_param.Path + + // 解析URL模板以查找路径参数 + urlParts := strings.Split(urlStr, "/") + for i, part := range urlParts { + if strings.Contains(part, "{") && strings.Contains(part, "}") { + for _, param := range tools_param.ParamName { + paramNameInPath := part[1 : len(part)-1] + if paramNameInPath == param { + if value, ok := data[param]; ok { + // 删除已经使用过的 + delete(data, param) + // 替换模板中的占位符 + urlParts[i] = url.QueryEscape(value.(string)) + } + } + } } } - // 组装 URL 和请求体 - url = apisParam.URL + tools_param.Path + key + // 重新组合URL + urlStr = strings.Join(urlParts, "/") + + queryParams := make([][2]string, 0) if method == "GET" { - queryParams := make([]string, 0, len(tools_param.ParamName)) for _, param := range tools_param.ParamName { if value, ok := data[param]; ok { - queryParams = append(queryParams, fmt.Sprintf("%s=%v", param, value)) + queryParams = append(queryParams, [2]string{param, fmt.Sprintf("%v", value)}) } } - if len(queryParams) > 0 { - url += "&" + strings.Join(queryParams, "&") - } } else if method == "POST" { var err error reqBody, err = json.Marshal(data) @@ -371,9 +464,30 @@ func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, raw } } - log.Infof("url: %s", url) + // 组装 headers 和 key + headers = [][2]string{{"Content-Type", "application/json"}} + if apisParam.APIKey.Name != "" { + if apisParam.APIKey.In == "query" { + queryParams = append(queryParams, [2]string{apisParam.APIKey.Name, apisParam.APIKey.Value}) + } else if apisParam.APIKey.In == "header" { + headers = append(headers, [2]string{"Authorization", apisParam.APIKey.Name + " " + apisParam.APIKey.Value}) + } + } + + if len(queryParams) > 0 { + // 将 key 拼接到 url 后面 + urlStr += "?" + for i, param := range queryParams { + if i != 0 { + urlStr += "&" + } + urlStr += url.QueryEscape(param[0]) + "=" + url.QueryEscape(param[1]) + } + } + + log.Debugf("url: %s", urlStr) - apiClient = config.APIClient[i] + apiClient = aPIClient[i] break } } @@ -382,11 +496,11 @@ func toolsCall(ctx wrapper.HttpContext, config PluginConfig, content string, raw if apiClient != nil { err := apiClient.Call( method, - url, + urlStr, headers, reqBody, func(statusCode int, responseHeaders http.Header, responseBody []byte) { - toolsCallResult(ctx, config, content, rawResponse, log, statusCode, responseBody) + toolsCallResult(ctx, llmClient, llmInfo, jsonResp, aPIsParam, aPIClient, content, rawResponse, log, statusCode, responseBody) }, uint32(maxExecutionTime)) if err != nil { log.Debugf("tool calls error: %s", err.Error()) @@ -415,7 +529,7 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config PluginConfig, body []byt //如果gpt返回的内容不是空的 if rawResponse.Choices[0].Message.Content != "" { //进入agent的循环思考,工具调用的过程中 - retType, _ := toolsCall(ctx, config, rawResponse.Choices[0].Message.Content, rawResponse, log) + retType, _ := toolsCall(ctx, config.LLMClient, config.LLMInfo, config.JsonResp, config.APIsParam, config.APIClient, rawResponse.Choices[0].Message.Content, rawResponse, log) return retType } else { return types.ActionContinue diff --git a/plugins/wasm-go/extensions/ai-agent/promptTpl/prompt.go b/plugins/wasm-go/extensions/ai-agent/promptTpl/prompt.go index 47ae6f71fc..a66cbdcfa5 100644 --- a/plugins/wasm-go/extensions/ai-agent/promptTpl/prompt.go +++ b/plugins/wasm-go/extensions/ai-agent/promptTpl/prompt.go @@ -167,3 +167,7 @@ Action:` + "```" + ` %s Question: %s ` +const Json_Resp_Template = ` +Given the Json Schema: %s, please help me convert the following content to a pure json: %s +Do not respond other content except the pure json!!!! +` From 4d1a0379426155a6b3ef120406af7785816e2932 Mon Sep 17 00:00:00 2001 From: littlejian <17816869670@163.com> Date: Mon, 11 Nov 2024 11:34:38 +0800 Subject: [PATCH 57/64] feat: Automatically generating markdown documentation for helm charts with helm-docs (#1496) --- .github/workflows/helm-docs.yaml | 35 ++++ helm/core/values.yaml | 214 ++++++++++---------- helm/higress/README.md | 333 +++++++++++++++++++++++++------ helm/higress/README.md.gotmpl | 34 ++++ 4 files changed, 459 insertions(+), 157 deletions(-) create mode 100644 .github/workflows/helm-docs.yaml create mode 100644 helm/higress/README.md.gotmpl diff --git a/.github/workflows/helm-docs.yaml b/.github/workflows/helm-docs.yaml new file mode 100644 index 0000000000..5111f83ef4 --- /dev/null +++ b/.github/workflows/helm-docs.yaml @@ -0,0 +1,35 @@ +name: "Helm Docs" + +on: + pull_request: + branches: + - "*" + + push: + +jobs: + + helm: + name: Helm Docs + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.22.9' + + - name: Run helm-docs + run: | + GOBIN=$PWD GO111MODULE=on go install github.com/norwoodj/helm-docs/cmd/helm-docs@v1.14.2 + ./helm-docs -c ${GITHUB_WORKSPACE}/helm/higress -f ../core/values.yaml + DIFF=$(git diff ${GITHUB_WORKSPACE}/helm/higress/*md) + if [ ! -z "$DIFF" ]; then + echo "Please use helm-docs in your clone, of your fork, of the project, and commit a updated README.md for the chart." + fi + git diff --exit-code + rm -f ./helm-docs \ No newline at end of file diff --git a/helm/core/values.yaml b/helm/core/values.yaml index fb03a2f85f..e0b818bb12 100644 --- a/helm/core/values.yaml +++ b/helm/core/values.yaml @@ -10,7 +10,7 @@ global: onDemandRDS: false hostRDSMergeSubset: false onlyPushRouteCluster: true - # IngressClass filters which ingress resources the higress controller watches. + # -- IngressClass filters which ingress resources the higress controller watches. # The default ingress class is higress. # There are some special cases for special ingress class. # 1. When the ingress class is set as nginx, the higress controller will watch ingress @@ -18,28 +18,40 @@ global: # 2. When the ingress class is set empty, the higress controller will watch all ingress # resources in the k8s cluster. ingressClass: "higress" + # -- If not empty, Higress Controller will only watch resources in the specified namespace. + # When isolating different business systems using K8s namespace, + # if each namespace requires a standalone gateway instance, + # this parameter can be used to confine the Ingress watching of Higress within the given namespace. watchNamespace: "" + # -- Whether to disable HTTP/2 in ALPN disableAlpnH2: false + # -- 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. enableStatus: true - # whether to use autoscaling/v2 template for HPA settings + # -- whether to use autoscaling/v2 template for HPA settings # for internal usage only, not to be configured by users. autoscalingv2API: true - local: false # When deploying to a local cluster (e.g.: kind cluster), set this to true. + # -- When deploying to a local cluster (e.g.: kind cluster), set this to true. + local: false kind: false # Deprecated. Please use "global.local" instead. Will be removed later. + # -- If true, Higress Controller will monitor istio resources as well enableIstioAPI: true + # -- If true, Higress Controller will monitor Gateway API resources as well enableGatewayAPI: false # Deprecated enableHigressIstio: false - # Used to locate istiod. + # -- Used to locate istiod. istioNamespace: istio-system - # enable pod disruption budget for the control plane, which is used to + # -- enable pod disruption budget for the control plane, which is used to # ensure Istio control plane components are gradually upgraded or recovered. defaultPodDisruptionBudget: enabled: false # The values aren't mutable due to a current PodDisruptionBudget limitation # minAvailable: 1 - # A minimal set of requested resources to applied to all deployments so that + # -- A minimal set of requested resources to applied to all deployments so that # Horizontal Pod Autoscaler will be able to function (if set). # Each component can overwrite these default values by adding its own resources # block in the relevant section below and setting the desired resources values. @@ -51,16 +63,16 @@ global: # cpu: 100m # memory: 128Mi - # Default hub for Istio images. + # -- Default hub for Istio images. # Releases are published to docker hub under 'istio' project. # Dev builds from prow are on gcr.io hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress - # Specify image pull policy if default behavior isn't desired. + # -- Specify image pull policy if default behavior isn't desired. # Default behavior: latest images will be Always else IfNotPresent. imagePullPolicy: "" - # ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace + # -- ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace # to use for pulling any images in pods that reference this ServiceAccount. # For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) # ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. @@ -68,14 +80,14 @@ global: imagePullSecrets: [] # - private-registry-key - # Enabled by default in master for maximising testing. + # -- Enabled by default in master for maximising testing. istiod: enableAnalysis: false - # To output all istio components logs in json format by adding --log_as_json argument to each container argument + # -- To output all istio components logs in json format by adding --log_as_json argument to each container argument logAsJson: false - # Comma-separated minimum per-scope logging level of messages to output, in the form of :,: + # -- Comma-separated minimum per-scope logging level of messages to output, in the form of :,: # The control plane has different scopes depending on component, but can configure default log level across all components # If empty, default scope and level will be used as configured in code logging: @@ -83,11 +95,11 @@ global: omitSidecarInjectorConfigMap: false - # Whether to restrict the applications namespace the controller manages; + # -- Whether to restrict the applications namespace the controller manages; # If not set, controller watches all namespaces oneNamespace: false - # Configure whether Operator manages webhook configurations. The current behavior + # -- Configure whether Operator manages webhook configurations. The current behavior # of Istiod is to manage its own webhook configurations. # When this option is set as true, Istio Operator, instead of webhooks, manages the # webhook configurations. When this option is set as false, webhooks manage their @@ -106,7 +118,7 @@ global: #- global #- "{{ valueOrDefault .DeploymentMeta.Namespace \"default\" }}.global" - # Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and + # -- Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and # system-node-critical, it is better to configure this in order to make sure your Istio pods # will not be killed because of low priority class. # Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass @@ -116,18 +128,18 @@ global: proxy: image: proxyv2 - # This controls the 'policy' in the sidecar injector. + # -- This controls the 'policy' in the sidecar injector. autoInject: enabled - # CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value + # -- CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value # cluster domain. Default value is "cluster.local". clusterDomain: "cluster.local" - # Per Component log level for proxy, applies to gateways and sidecars. If a component level is + # -- Per Component log level for proxy, applies to gateways and sidecars. If a component level is # not set, then the global "logLevel" will be used. componentLogLevel: "misc:error" - # If set, newly injected sidecars will have core dumps enabled. + # -- If set, newly injected sidecars will have core dumps enabled. enableCoreDump: false # istio ingress capture allowlist @@ -136,7 +148,7 @@ global: excludeInboundPorts: "" includeInboundPorts: "*" - # istio egress capture allowlist + # -- istio egress capture allowlist # https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly # example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16" # would only capture egress traffic on those two IP Ranges, all other outbound traffic would @@ -146,29 +158,29 @@ global: includeOutboundPorts: "" excludeOutboundPorts: "" - # Log level for proxy, applies to gateways and sidecars. + # -- Log level for proxy, applies to gateways and sidecars. # Expected values are: trace|debug|info|warning|error|critical|off logLevel: warning - #If set to true, istio-proxy container will have privileged securityContext + # -- If set to true, istio-proxy container will have privileged securityContext privileged: false - # The number of successive failed probes before indicating readiness failure. + # -- The number of successive failed probes before indicating readiness failure. readinessFailureThreshold: 30 - # The number of successive successed probes before indicating readiness success. + # -- The number of successive successed probes before indicating readiness success. readinessSuccessThreshold: 30 - # The initial delay for readiness probes in seconds. + # -- The initial delay for readiness probes in seconds. readinessInitialDelaySeconds: 1 - # The period between readiness probes. + # -- The period between readiness probes. readinessPeriodSeconds: 2 - # The readiness timeout seconds + # -- The readiness timeout seconds readinessTimeoutSeconds: 3 - # Resources for the sidecar. + # -- Resources for the sidecar. resources: requests: cpu: 100m @@ -177,18 +189,18 @@ global: cpu: 2000m memory: 1024Mi - # Default port for Pilot agent health checks. A value of 0 will disable health checking. + # -- Default port for Pilot agent health checks. A value of 0 will disable health checking. statusPort: 15020 - # Specify which tracer to use. One of: lightstep, datadog, stackdriver. + # -- Specify which tracer to use. One of: lightstep, datadog, stackdriver. # If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file. tracer: "" - # Controls if sidecar is injected at the front of the container list and blocks the start of the other containers until the proxy is ready + # -- Controls if sidecar is injected at the front of the container list and blocks the start of the other containers until the proxy is ready holdApplicationUntilProxyStarts: false proxy_init: - # Base name for the proxy_init container, used to configure iptables. + # -- Base name for the proxy_init container, used to configure iptables. image: proxyv2 resources: limits: @@ -198,7 +210,7 @@ global: cpu: 10m memory: 10Mi - # configure remote pilot and istiod service and endpoint + # -- configure remote pilot and istiod service and endpoint remotePilotAddress: "" ############################################################################################## @@ -206,20 +218,20 @@ global: # make sure they are consistent across your Istio helm charts # ############################################################################################## - # The customized CA address to retrieve certificates for the pods in the cluster. + # -- The customized CA address to retrieve certificates for the pods in the cluster. # CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. # If not set explicitly, default to the Istio discovery address. caAddress: "" - # Configure a remote cluster data plane controlled by an external istiod. + # -- 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. externalIstiod: false - # Configure a remote cluster as the config cluster for an external istiod. + # -- Configure a remote cluster as the config cluster for an external istiod. configCluster: false - # Configure the policy for validating JWT. + # -- Configure the policy for validating JWT. # Currently, two options are supported: "third-party-jwt" and "first-party-jwt". jwtPolicy: "third-party-jwt" @@ -241,7 +253,7 @@ global: # of migration TBD, and it may be a disruptive operation to change the Mesh # ID post-install. # - # If the mesh admin does not specify a value, Istio will use the value of the + # -- If the mesh admin does not specify a value, Istio will use the value of the # mesh's Trust Domain. The best practice is to select a proper Trust Domain # value. meshID: "" @@ -275,68 +287,69 @@ global: # meshNetworks: {} - # Use the user-specified, secret volume mounted key and certs for Pilot and workloads. + # -- Use the user-specified, secret volume mounted key and certs for Pilot and workloads. mountMtlsCerts: false multiCluster: - # Set to true to connect two kubernetes clusters via their respective + # -- Set to true to connect two kubernetes clusters via their respective # ingressgateway services when pods in each cluster cannot directly # talk to one another. All clusters should be using Istio mTLS and must # have a shared root CA for this model to work. enabled: true - # Should be set to the name of the cluster this installation will run in. This is required for sidecar injection + # -- Should be set to the name of the cluster this installation will run in. This is required for sidecar injection # to properly label proxies clusterName: "" - # Network defines the network this cluster belong to. This name + # -- Network defines the network this cluster belong to. This name # corresponds to the networks in the map of mesh networks. network: "" - # Configure the certificate provider for control plane communication. + # -- Configure the certificate provider for control plane communication. # Currently, two providers are supported: "kubernetes" and "istiod". # As some platforms may not have kubernetes signing APIs, # Istiod is the default pilotCertProvider: istiod sds: - # The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. + # -- The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. # When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the # JWT is intended for the CA. token: aud: istio-ca sts: - # The service port used by Security Token Service (STS) server to handle token exchange requests. + # -- The service port used by Security Token Service (STS) server to handle token exchange requests. # Setting this port to a non-zero value enables STS server. servicePort: 0 - # Configuration for each of the supported tracers + # -- Configuration for each of the supported tracers tracer: - # Configuration for envoy to send trace data to LightStep. + # -- Configuration for envoy to send trace data to LightStep. # Disabled by default. # address: the : of the satellite pool # accessToken: required for sending data to the pool # datadog: - # Host:Port for submitting traces to the Datadog agent. + # -- Host:Port for submitting traces to the Datadog agent. address: "$(HOST_IP):8126" lightstep: - address: "" # example: lightstep-satellite:443 - accessToken: "" # example: abcdefg1234567 + # -- example: lightstep-satellite:443 + address: "" + # -- example: abcdefg1234567 + accessToken: "" stackdriver: - # enables trace output to stdout. + # -- enables trace output to stdout. debug: false - # The global default max number of message events per span. + # -- The global default max number of message events per span. maxNumberOfMessageEvents: 200 - # The global default max number of annotation events per span. + # -- The global default max number of annotation events per span. maxNumberOfAnnotations: 200 - # The global default max number of attributes per span. + # -- The global default max number of attributes per span. maxNumberOfAttributes: 200 - # Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source. - + # -- Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source. useMCP: false - # Observability (o11y) configurations + # -- Observability (o11y) configurations o11y: enabled: false promtail: @@ -350,7 +363,7 @@ global: memory: 2Gi securityContext: {} - # The name of the CA for workload certificates. + # -- The name of the CA for workload certificates. # For example, when caName=GkeWorkloadCertificate, GKE workload certificates # will be used as the certificates for workloads. # The default value is "" and when caName="", the CA will be configured by other @@ -359,7 +372,7 @@ global: hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress clusterName: "" -# meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior +# -- meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior # See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options meshConfig: enablePrometheusMerge: true @@ -370,14 +383,13 @@ meshConfig: # and gradual adoption by setting capture only on specific workloads. It also allows # VMs to use other DNS options, like dnsmasq or unbound. - # The namespace to treat as the administrative root namespace for Istio configuration. + # -- The namespace to treat as the administrative root namespace for Istio configuration. # When processing a leaf namespace Istio will search for declarations in that namespace first # and if none are found it will search in the root namespace. Any matching declaration found in the root namespace # is processed as if it were declared in the leaf namespace. - rootNamespace: - # The trust domain corresponds to the trust root of a system + # -- The trust domain corresponds to the trust root of a system # Refer to https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain trustDomain: "cluster.local" @@ -391,56 +403,57 @@ meshConfig: gateway: name: "higress-gateway" + # -- Number of Higress Gateway pods replicas: 2 image: gateway # -- Use a `DaemonSet` or `Deployment` kind: Deployment - # The number of successive failed probes before indicating readiness failure. + # -- The number of successive failed probes before indicating readiness failure. readinessFailureThreshold: 30 - # The number of successive successed probes before indicating readiness success. + # -- The number of successive successed probes before indicating readiness success. readinessSuccessThreshold: 1 - # The initial delay for readiness probes in seconds. + # -- The initial delay for readiness probes in seconds. readinessInitialDelaySeconds: 1 - # The period between readiness probes. + # -- The period between readiness probes. readinessPeriodSeconds: 2 - # The readiness timeout seconds + # -- The readiness timeout seconds readinessTimeoutSeconds: 3 hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress tag: "" - # revision declares which revision this gateway is a part of + # -- revision declares which revision this gateway is a part of revision: "" rbac: - # If enabled, roles will be created to enable accessing certificates from Gateways. This is not needed + # -- If enabled, roles will be created to enable accessing certificates from Gateways. This is not needed # when using http://gateway-api.org/. enabled: true serviceAccount: - # If set, a service account will be created. Otherwise, the default is used + # -- If set, a service account will be created. Otherwise, the default is used create: true - # Annotations to add to the service account + # -- Annotations to add to the service account annotations: {} - # The name of the service account to use. + # -- The name of the service account to use. # If not set, the release name is used name: "" - # Pod environment variables + # -- Pod environment variables env: {} httpPort: 80 httpsPort: 443 hostNetwork: false - # Labels to apply to all resources + # -- Labels to apply to all resources labels: {} - # Annotations to apply to all resources + # -- Annotations to apply to all resources annotations: {} podAnnotations: @@ -449,14 +462,14 @@ gateway: prometheus.io/path: "/stats/prometheus" sidecar.istio.io/inject: "false" - # Define the security context for the pod. + # -- Define the security context for the pod. # If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443. # On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl. securityContext: ~ containerSecurityContext: ~ service: - # Type of service. Set to "None" to disable the service entirely + # -- Type of service. Set to "None" to disable the service entirely type: LoadBalancer ports: - name: http2 @@ -496,28 +509,29 @@ gateway: affinity: {} - # If specified, the gateway will act as a network gateway for the given network. + # -- If specified, the gateway will act as a network gateway for the given network. networkGateway: "" metrics: - # If true, create PodMonitor or VMPodScrape for gateway + # -- If true, create PodMonitor or VMPodScrape for gateway enabled: false - # provider group name for CustomResourceDefinition, can be monitoring.coreos.com or operator.victoriametrics.com + # -- provider group name for CustomResourceDefinition, can be monitoring.coreos.com or operator.victoriametrics.com provider: monitoring.coreos.com interval: "" scrapeTimeout: "" honorLabels: false - # for monitoring.coreos.com/v1.PodMonitor + # -- for monitoring.coreos.com/v1.PodMonitor metricRelabelings: [] relabelings: [] - # for operator.victoriametrics.com/v1beta1.VMPodScrape + # -- for operator.victoriametrics.com/v1beta1.VMPodScrape metricRelabelConfigs: [] relabelConfigs: [] - # some more raw podMetricsEndpoints spec + # -- some more raw podMetricsEndpoints spec rawSpec: {} controller: name: "higress-controller" + # -- Number of Higress Controller pods replicas: 1 image: higress @@ -541,12 +555,12 @@ controller: create: true serviceAccount: - # Specifies whether a service account should be created + # -- Specifies whether a service account should be created create: true - # Annotations to add to the service account + # -- Annotations to add to the service account annotations: {} - # The name of the service account to use. - # If not set and create is true, a name is generated using the fullname template + # -- The name of the service account to use. + # -- If not set and create is true, a name is generated using the fullname template name: "" podAnnotations: {} @@ -602,7 +616,7 @@ controller: enabled: true email: "" -## Discovery Settings +## -- Discovery Settings pilot: autoscaleEnabled: false autoscaleMin: 1 @@ -614,11 +628,11 @@ pilot: hub: higress-registry.cn-hangzhou.cr.aliyuncs.com/higress tag: "" - # Can be a full hub/image:tag + # -- Can be a full hub/image:tag image: pilot traceSampling: 1.0 - # Resources for a small pilot install + # -- Resources for a small pilot install resources: requests: cpu: 500m @@ -633,21 +647,21 @@ pilot: cpu: targetAverageUtilization: 80 - # if protocol sniffing is enabled for outbound + # -- if protocol sniffing is enabled for outbound enableProtocolSniffingForOutbound: true - # if protocol sniffing is enabled for inbound + # -- if protocol sniffing is enabled for inbound enableProtocolSniffingForInbound: true nodeSelector: {} podAnnotations: {} serviceAnnotations: {} - # You can use jwksResolverExtraRootCA to provide a root certificate + # -- You can use jwksResolverExtraRootCA to provide a root certificate # in PEM format. This will then be trusted by pilot when resolving # JWKS URIs. jwksResolverExtraRootCA: "" - # This is used to set the source of configuration for + # -- This is used to set the source of configuration for # the associated address in configSource, if nothing is specified # the default MCP is assumed. configSource: @@ -655,21 +669,21 @@ pilot: plugins: [] - # The following is used to limit how long a sidecar can be connected + # -- The following is used to limit how long a sidecar can be connected # to a pilot. It balances out load across pilot instances at the cost of # increasing system churn. keepaliveMaxServerConnectionAge: 30m - # Additional labels to apply to the deployment. + # -- Additional labels to apply to the deployment. deploymentLabels: {} ## Mesh config settings - # Install the mesh config map, generated from values.yaml. + # -- Install the mesh config map, generated from values.yaml. # If false, pilot wil use default values (by default) or user-supplied values. configMap: true - # Additional labels to apply on the pod level for monitoring and logging configuration. + # -- Additional labels to apply on the pod level for monitoring and logging configuration. podLabels: {} # Tracing config settings @@ -685,7 +699,7 @@ tracing: # service: "" # port: 9411 -# Downstream config settings +# -- Downstream config settings downstream: idleTimeout: 180 maxRequestHeadersKb: 60 @@ -696,7 +710,7 @@ downstream: initialConnectionWindowSize: 1048576 routeTimeout: 0 -# Upstream config settings +# -- Upstream config settings upstream: idleTimeout: 10 connectionBufferLimits: 10485760 diff --git a/helm/higress/README.md b/helm/higress/README.md index 6204b7a1ec..45afff63d6 100644 --- a/helm/higress/README.md +++ b/helm/higress/README.md @@ -1,57 +1,276 @@ -# Higress Helm Chart - -Installs the cloud-native gateway [Higress](http://higress.io/) - -## Get Repo Info - -```console -helm repo add higress.io https://higress.io/helm-charts -helm repo update -``` - -_See [helm repo](https://helm.sh/docs/helm/helm_repo/) for command documentation._ - -## Installing the Chart - -To install the chart with the release name `higress`: - -```console -helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes -``` - -## Uninstalling the Chart - -To uninstall/delete the higress deployment: - -```console -helm delete higress -n higress-system -``` - -The command removes all the Kubernetes components associated with the chart and deletes the release. - -## Configuration - -| **Parameter** | **Description** | **Default** | -|---|---|---| -| **Global Parameters** | | | -| global.local | Set to `true` if installing to a local K8s cluster (e.g.: Kind, Rancher Desktop, etc.) | false | -| global.ingressClass | [IngressClass](https://kubernetes.io/zh-cn/docs/concepts/services-networking/ingress/#ingress-class) which is used to filter Ingress resources Higress Controller watches.
If there are multiple gateway instances deployed in the cluster, this parameter can be used to distinguish the scope of each gateway instance.
There are some special cases for special IngressClass values:
1. If set to "nginx", Higress Controller will watch Ingress resources with the `nginx` IngressClass or without any Ingress class.
2. If set to empty, Higress Controller will watch all Ingress resources in the K8s cluster. | higress | -| global.watchNamespace | If not empty, Higress Controller will only watch resources in the specified namespace. When isolating different business systems using K8s namespace, if each namespace requires a standalone gateway instance, this parameter can be used to confine the Ingress watching of Higress within the given namespace. | "" | -| global.disableAlpnH2 | Whether to disable HTTP/2 in ALPN | true | -| global.enableStatus | 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. | true | -| global.enableIstioAPI | If `true`, Higress Controller will monitor istio resources as well | false | -| global.enableGatewayAPI | If `true`, Higress Controller will monitor Gateway API resources as well | false | -| global.istioNamespace | The namespace istio is installed to | istio-system | -| **Core Paramters** | | | -| higress-core.gateway.replicas | Number of Higress Gateway pods | 2 | -| higress-core.controller.replicas | Number of Higress Controller pods | 1 | -| **Console Paramters** | | | -| higress-console.replicaCount | Number of Higress Console pods | 1 | -| higress-console.service.type | K8s service type used by Higress Console | ClusterIP | -| higress-console.domain | Domain used to access Higress Console | console.higress.io | -| higress-console.tlsSecretName | Name of Secret resource used by TLS connections. | "" | -| higress-console.web.login.prompt | Prompt message to be displayed on the login page | "" | -| higress-console.admin.password.value | If not empty, the admin password will be configured to the specified value. | "" | -| higress-console.admin.password.length | The length of random admin password generated during installation. Only works when `higress-console.admin.password.value` is not set. | 8 | -| higress-console.o11y.enabled | If `true`, o11y suite (Grafana + Promethues) will be installed. | false | -| higress-console.pvc.rwxSupported | Set to `false` when installing to a standard K8s cluster and the target cluster doesn't support the ReadWriteMany access mode of PersistentVolumeClaim. | true | +## Higress for Kubernetes + +Higress is a cloud-native api gateway based on Alibaba's internal gateway practices. + +Powered by Istio and 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. + +## Setup Repo Info + +```console +helm repo add higress.io https://higress.io/helm-charts +helm repo update +``` + +## Install + +To install the chart with the release name `higress`: + +```console +helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes +``` + +## Uninstall + +To uninstall/delete the higress deployment: + +```console +helm delete higress -n higress-system +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +## Values + +| Key | Type | Default | Description | +|-----|------|---------|-------------| +| clusterName | string | `""` | | +| controller.affinity | object | `{}` | | +| controller.automaticHttps.email | string | `""` | | +| controller.automaticHttps.enabled | bool | `true` | | +| controller.autoscaling.enabled | bool | `false` | | +| controller.autoscaling.maxReplicas | int | `5` | | +| controller.autoscaling.minReplicas | int | `1` | | +| controller.autoscaling.targetCPUUtilizationPercentage | int | `80` | | +| controller.env | object | `{}` | | +| controller.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | | +| controller.image | string | `"higress"` | | +| controller.imagePullSecrets | list | `[]` | | +| controller.labels | object | `{}` | | +| controller.name | string | `"higress-controller"` | | +| controller.nodeSelector | object | `{}` | | +| controller.podAnnotations | object | `{}` | | +| controller.podSecurityContext | object | `{}` | | +| controller.ports[0].name | string | `"http"` | | +| controller.ports[0].port | int | `8888` | | +| controller.ports[0].protocol | string | `"TCP"` | | +| controller.ports[0].targetPort | int | `8888` | | +| controller.ports[1].name | string | `"http-solver"` | | +| controller.ports[1].port | int | `8889` | | +| controller.ports[1].protocol | string | `"TCP"` | | +| controller.ports[1].targetPort | int | `8889` | | +| controller.ports[2].name | string | `"grpc"` | | +| controller.ports[2].port | int | `15051` | | +| controller.ports[2].protocol | string | `"TCP"` | | +| controller.ports[2].targetPort | int | `15051` | | +| controller.probe.httpGet.path | string | `"/ready"` | | +| controller.probe.httpGet.port | int | `8888` | | +| controller.probe.initialDelaySeconds | int | `1` | | +| controller.probe.periodSeconds | int | `3` | | +| controller.probe.timeoutSeconds | int | `5` | | +| controller.rbac.create | bool | `true` | | +| controller.replicas | int | `1` | Number of Higress Controller pods | +| controller.resources.limits.cpu | string | `"1000m"` | | +| controller.resources.limits.memory | string | `"2048Mi"` | | +| controller.resources.requests.cpu | string | `"500m"` | | +| controller.resources.requests.memory | string | `"2048Mi"` | | +| controller.securityContext | object | `{}` | | +| controller.service.type | string | `"ClusterIP"` | | +| controller.serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| controller.serviceAccount.create | bool | `true` | Specifies whether a service account should be created | +| controller.serviceAccount.name | string | `""` | If not set and create is true, a name is generated using the fullname template | +| controller.tag | string | `""` | | +| controller.tolerations | list | `[]` | | +| downstream | object | `{"connectionBufferLimits":32768,"http2":{"initialConnectionWindowSize":1048576,"initialStreamWindowSize":65535,"maxConcurrentStreams":100},"idleTimeout":180,"maxRequestHeadersKb":60,"routeTimeout":0}` | Downstream config settings | +| gateway.affinity | object | `{}` | | +| gateway.annotations | object | `{}` | Annotations to apply to all resources | +| gateway.autoscaling.enabled | bool | `false` | | +| gateway.autoscaling.maxReplicas | int | `5` | | +| gateway.autoscaling.minReplicas | int | `1` | | +| gateway.autoscaling.targetCPUUtilizationPercentage | int | `80` | | +| gateway.containerSecurityContext | string | `nil` | | +| gateway.env | object | `{}` | Pod environment variables | +| gateway.hostNetwork | bool | `false` | | +| gateway.httpPort | int | `80` | | +| gateway.httpsPort | int | `443` | | +| gateway.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | | +| gateway.image | string | `"gateway"` | | +| gateway.kind | string | `"Deployment"` | Use a `DaemonSet` or `Deployment` | +| gateway.labels | object | `{}` | Labels to apply to all resources | +| gateway.metrics.enabled | bool | `false` | If true, create PodMonitor or VMPodScrape for gateway | +| gateway.metrics.honorLabels | bool | `false` | | +| gateway.metrics.interval | string | `""` | | +| gateway.metrics.metricRelabelConfigs | list | `[]` | for operator.victoriametrics.com/v1beta1.VMPodScrape | +| gateway.metrics.metricRelabelings | list | `[]` | for monitoring.coreos.com/v1.PodMonitor | +| gateway.metrics.provider | string | `"monitoring.coreos.com"` | provider group name for CustomResourceDefinition, can be monitoring.coreos.com or operator.victoriametrics.com | +| gateway.metrics.rawSpec | object | `{}` | some more raw podMetricsEndpoints spec | +| gateway.metrics.relabelConfigs | list | `[]` | | +| gateway.metrics.relabelings | list | `[]` | | +| gateway.metrics.scrapeTimeout | string | `""` | | +| gateway.name | string | `"higress-gateway"` | | +| gateway.networkGateway | string | `""` | If specified, the gateway will act as a network gateway for the given network. | +| gateway.nodeSelector | object | `{}` | | +| gateway.podAnnotations."prometheus.io/path" | string | `"/stats/prometheus"` | | +| gateway.podAnnotations."prometheus.io/port" | string | `"15020"` | | +| gateway.podAnnotations."prometheus.io/scrape" | string | `"true"` | | +| gateway.podAnnotations."sidecar.istio.io/inject" | string | `"false"` | | +| gateway.rbac.enabled | bool | `true` | If enabled, roles will be created to enable accessing certificates from Gateways. This is not needed when using http://gateway-api.org/. | +| gateway.readinessFailureThreshold | int | `30` | The number of successive failed probes before indicating readiness failure. | +| gateway.readinessInitialDelaySeconds | int | `1` | The initial delay for readiness probes in seconds. | +| gateway.readinessPeriodSeconds | int | `2` | The period between readiness probes. | +| gateway.readinessSuccessThreshold | int | `1` | The number of successive successed probes before indicating readiness success. | +| gateway.readinessTimeoutSeconds | int | `3` | The readiness timeout seconds | +| gateway.replicas | int | `2` | Number of Higress Gateway pods | +| gateway.resources.limits.cpu | string | `"2000m"` | | +| gateway.resources.limits.memory | string | `"2048Mi"` | | +| gateway.resources.requests.cpu | string | `"2000m"` | | +| gateway.resources.requests.memory | string | `"2048Mi"` | | +| gateway.revision | string | `""` | revision declares which revision this gateway is a part of | +| gateway.rollingMaxSurge | string | `"100%"` | | +| gateway.rollingMaxUnavailable | string | `"25%"` | | +| gateway.securityContext | string | `nil` | Define the security context for the pod. If unset, this will be automatically set to the minimum privileges required to bind to port 80 and 443. On Kubernetes 1.22+, this only requires the `net.ipv4.ip_unprivileged_port_start` sysctl. | +| gateway.service.annotations | object | `{}` | | +| gateway.service.externalTrafficPolicy | string | `""` | | +| gateway.service.loadBalancerClass | string | `""` | | +| gateway.service.loadBalancerIP | string | `""` | | +| gateway.service.loadBalancerSourceRanges | list | `[]` | | +| gateway.service.ports[0].name | string | `"http2"` | | +| gateway.service.ports[0].port | int | `80` | | +| gateway.service.ports[0].protocol | string | `"TCP"` | | +| gateway.service.ports[0].targetPort | int | `80` | | +| gateway.service.ports[1].name | string | `"https"` | | +| gateway.service.ports[1].port | int | `443` | | +| gateway.service.ports[1].protocol | string | `"TCP"` | | +| gateway.service.ports[1].targetPort | int | `443` | | +| gateway.service.type | string | `"LoadBalancer"` | Type of service. Set to "None" to disable the service entirely | +| gateway.serviceAccount.annotations | object | `{}` | Annotations to add to the service account | +| gateway.serviceAccount.create | bool | `true` | If set, a service account will be created. Otherwise, the default is used | +| gateway.serviceAccount.name | string | `""` | The name of the service account to use. If not set, the release name is used | +| gateway.tag | string | `""` | | +| gateway.tolerations | list | `[]` | | +| global.autoscalingv2API | bool | `true` | whether to use autoscaling/v2 template for HPA settings for internal usage only, not to be configured by users. | +| global.caAddress | string | `""` | The customized CA address to retrieve certificates for the pods in the cluster. CSR clients such as the Istio Agent and ingress gateways can use this to specify the CA endpoint. If not set explicitly, default to the Istio discovery address. | +| global.caName | string | `""` | The name of the CA for workload certificates. For example, when caName=GkeWorkloadCertificate, GKE workload certificates will be used as the certificates for workloads. The default value is "" and when caName="", the CA will be configured by other mechanisms (e.g., environmental variable CA_PROVIDER). | +| global.configCluster | bool | `false` | Configure a remote cluster as the config cluster for an external istiod. | +| global.defaultPodDisruptionBudget | object | `{"enabled":false}` | enable pod disruption budget for the control plane, which is used to ensure Istio control plane components are gradually upgraded or recovered. | +| global.defaultResources | object | `{"requests":{"cpu":"10m"}}` | A minimal set of requested resources to applied to all deployments so that Horizontal Pod Autoscaler will be able to function (if set). Each component can overwrite these default values by adding its own resources block in the relevant section below and setting the desired resources values. | +| global.defaultUpstreamConcurrencyThreshold | int | `10000` | | +| global.disableAlpnH2 | bool | `false` | Whether to disable HTTP/2 in ALPN | +| global.enableGatewayAPI | bool | `false` | If true, Higress Controller will monitor Gateway API resources as well | +| global.enableH3 | bool | `false` | | +| global.enableHigressIstio | bool | `false` | | +| global.enableIPv6 | bool | `false` | | +| global.enableIstioAPI | bool | `true` | If true, Higress Controller will monitor istio resources as well | +| global.enableProxyProtocol | bool | `false` | | +| 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. | +| global.hostRDSMergeSubset | bool | `false` | | +| global.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | Default hub for Istio images. Releases are published to docker hub under 'istio' project. Dev builds from prow are on gcr.io | +| global.imagePullPolicy | string | `""` | Specify image pull policy if default behavior isn't desired. Default behavior: latest images will be Always else IfNotPresent. | +| global.imagePullSecrets | list | `[]` | ImagePullSecrets for all ServiceAccount, list of secrets in the same namespace to use for pulling any images in pods that reference this ServiceAccount. For components that don't use ServiceAccounts (i.e. grafana, servicegraph, tracing) ImagePullSecrets will be added to the corresponding Deployment(StatefulSet) objects. Must be set for any cluster configured with private docker registry. | +| global.ingressClass | string | `"higress"` | IngressClass filters which ingress resources the higress controller watches. The default ingress class is higress. There are some special cases for special ingress class. 1. When the ingress class is set as nginx, the higress controller will watch ingress resources with the nginx ingress class or without any ingress class. 2. When the ingress class is set empty, the higress controller will watch all ingress resources in the k8s cluster. | +| global.istioNamespace | string | `"istio-system"` | Used to locate istiod. | +| global.istiod | object | `{"enableAnalysis":false}` | Enabled by default in master for maximising testing. | +| global.jwtPolicy | string | `"third-party-jwt"` | Configure the policy for validating JWT. Currently, two options are supported: "third-party-jwt" and "first-party-jwt". | +| global.kind | bool | `false` | | +| global.liteMetrics | bool | `true` | | +| global.local | bool | `false` | When deploying to a local cluster (e.g.: kind cluster), set this to true. | +| global.logAsJson | bool | `false` | | +| global.logging | object | `{"level":"default:info"}` | Comma-separated minimum per-scope logging level of messages to output, in the form of :,: The control plane has different scopes depending on component, but can configure default log level across all components If empty, default scope and level will be used as configured in code | +| global.meshID | string | `""` | If the mesh admin does not specify a value, Istio will use the value of the mesh's Trust Domain. The best practice is to select a proper Trust Domain value. | +| global.meshNetworks | object | `{}` | | +| global.mountMtlsCerts | bool | `false` | Use the user-specified, secret volume mounted key and certs for Pilot and workloads. | +| global.multiCluster.clusterName | string | `""` | Should be set to the name of the cluster this installation will run in. This is required for sidecar injection to properly label proxies | +| global.multiCluster.enabled | bool | `true` | Set to true to connect two kubernetes clusters via their respective ingressgateway services when pods in each cluster cannot directly talk to one another. All clusters should be using Istio mTLS and must have a shared root CA for this model to work. | +| global.network | string | `""` | Network defines the network this cluster belong to. This name corresponds to the networks in the map of mesh networks. | +| global.o11y | object | `{"enabled":false,"promtail":{"image":{"repository":"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress/promtail","tag":"2.9.4"},"port":3101,"resources":{"limits":{"cpu":"500m","memory":"2Gi"}},"securityContext":{}}}` | Observability (o11y) configurations | +| global.omitSidecarInjectorConfigMap | bool | `false` | | +| global.onDemandRDS | bool | `false` | | +| global.oneNamespace | bool | `false` | Whether to restrict the applications namespace the controller manages; If not set, controller watches all namespaces | +| global.onlyPushRouteCluster | bool | `true` | | +| global.operatorManageWebhooks | bool | `false` | Configure whether Operator manages webhook configurations. The current behavior of Istiod is to manage its own webhook configurations. When this option is set as true, Istio Operator, instead of webhooks, manages the webhook configurations. When this option is set as false, webhooks manage their own webhook configurations. | +| global.pilotCertProvider | string | `"istiod"` | Configure the certificate provider for control plane communication. Currently, two providers are supported: "kubernetes" and "istiod". As some platforms may not have kubernetes signing APIs, Istiod is the default | +| global.priorityClassName | string | `""` | Kubernetes >=v1.11.0 will create two PriorityClass, including system-cluster-critical and system-node-critical, it is better to configure this in order to make sure your Istio pods will not be killed because of low priority class. Refer to https://kubernetes.io/docs/concepts/configuration/pod-priority-preemption/#priorityclass for more detail. | +| global.proxy.autoInject | string | `"enabled"` | This controls the 'policy' in the sidecar injector. | +| global.proxy.clusterDomain | string | `"cluster.local"` | CAUTION: It is important to ensure that all Istio helm charts specify the same clusterDomain value cluster domain. Default value is "cluster.local". | +| global.proxy.componentLogLevel | string | `"misc:error"` | Per Component log level for proxy, applies to gateways and sidecars. If a component level is not set, then the global "logLevel" will be used. | +| global.proxy.enableCoreDump | bool | `false` | If set, newly injected sidecars will have core dumps enabled. | +| global.proxy.excludeIPRanges | string | `""` | | +| global.proxy.excludeInboundPorts | string | `""` | | +| global.proxy.excludeOutboundPorts | string | `""` | | +| global.proxy.holdApplicationUntilProxyStarts | bool | `false` | Controls if sidecar is injected at the front of the container list and blocks the start of the other containers until the proxy is ready | +| global.proxy.image | string | `"proxyv2"` | | +| global.proxy.includeIPRanges | string | `"*"` | istio egress capture allowlist https://istio.io/docs/tasks/traffic-management/egress.html#calling-external-services-directly example: includeIPRanges: "172.30.0.0/16,172.20.0.0/16" would only capture egress traffic on those two IP Ranges, all other outbound traffic would be allowed by the sidecar | +| global.proxy.includeInboundPorts | string | `"*"` | | +| global.proxy.includeOutboundPorts | string | `""` | | +| global.proxy.logLevel | string | `"warning"` | Log level for proxy, applies to gateways and sidecars. Expected values are: trace|debug|info|warning|error|critical|off | +| global.proxy.privileged | bool | `false` | If set to true, istio-proxy container will have privileged securityContext | +| global.proxy.readinessFailureThreshold | int | `30` | The number of successive failed probes before indicating readiness failure. | +| global.proxy.readinessInitialDelaySeconds | int | `1` | The initial delay for readiness probes in seconds. | +| global.proxy.readinessPeriodSeconds | int | `2` | The period between readiness probes. | +| global.proxy.readinessSuccessThreshold | int | `30` | The number of successive successed probes before indicating readiness success. | +| global.proxy.readinessTimeoutSeconds | int | `3` | The readiness timeout seconds | +| global.proxy.resources | object | `{"limits":{"cpu":"2000m","memory":"1024Mi"},"requests":{"cpu":"100m","memory":"128Mi"}}` | Resources for the sidecar. | +| global.proxy.statusPort | int | `15020` | Default port for Pilot agent health checks. A value of 0 will disable health checking. | +| global.proxy.tracer | string | `""` | Specify which tracer to use. One of: lightstep, datadog, stackdriver. If using stackdriver tracer outside GCP, set env GOOGLE_APPLICATION_CREDENTIALS to the GCP credential file. | +| global.proxy_init.image | string | `"proxyv2"` | Base name for the proxy_init container, used to configure iptables. | +| global.proxy_init.resources.limits.cpu | string | `"2000m"` | | +| global.proxy_init.resources.limits.memory | string | `"1024Mi"` | | +| global.proxy_init.resources.requests.cpu | string | `"10m"` | | +| global.proxy_init.resources.requests.memory | string | `"10Mi"` | | +| global.remotePilotAddress | string | `""` | configure remote pilot and istiod service and endpoint | +| global.sds.token | object | `{"aud":"istio-ca"}` | The JWT token for SDS and the aud field of such JWT. See RFC 7519, section 4.1.3. When a CSR is sent from Istio Agent to the CA (e.g. Istiod), this aud is to make sure the JWT is intended for the CA. | +| global.sts.servicePort | int | `0` | The service port used by Security Token Service (STS) server to handle token exchange requests. Setting this port to a non-zero value enables STS server. | +| global.tracer | object | `{"datadog":{"address":"$(HOST_IP):8126"},"lightstep":{"accessToken":"","address":""},"stackdriver":{"debug":false,"maxNumberOfAnnotations":200,"maxNumberOfAttributes":200,"maxNumberOfMessageEvents":200}}` | Configuration for each of the supported tracers | +| global.tracer.datadog | object | `{"address":"$(HOST_IP):8126"}` | Configuration for envoy to send trace data to LightStep. Disabled by default. address: the : of the satellite pool accessToken: required for sending data to the pool | +| global.tracer.datadog.address | string | `"$(HOST_IP):8126"` | Host:Port for submitting traces to the Datadog agent. | +| global.tracer.lightstep.accessToken | string | `""` | example: abcdefg1234567 | +| global.tracer.lightstep.address | string | `""` | example: lightstep-satellite:443 | +| global.tracer.stackdriver.debug | bool | `false` | enables trace output to stdout. | +| global.tracer.stackdriver.maxNumberOfAnnotations | int | `200` | The global default max number of annotation events per span. | +| global.tracer.stackdriver.maxNumberOfAttributes | int | `200` | The global default max number of attributes per span. | +| global.tracer.stackdriver.maxNumberOfMessageEvents | int | `200` | The global default max number of message events per span. | +| global.useMCP | bool | `false` | Use the Mesh Control Protocol (MCP) for configuring Istiod. Requires an MCP source. | +| global.watchNamespace | string | `""` | If not empty, Higress Controller will only watch resources in the specified namespace. When isolating different business systems using K8s namespace, if each namespace requires a standalone gateway instance, this parameter can be used to confine the Ingress watching of Higress within the given namespace. | +| global.xdsMaxRecvMsgSize | string | `"104857600"` | | +| hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | | +| meshConfig | object | `{"enablePrometheusMerge":true,"rootNamespace":null,"trustDomain":"cluster.local"}` | meshConfig defines runtime configuration of components, including Istiod and istio-agent behavior See https://istio.io/docs/reference/config/istio.mesh.v1alpha1/ for all available options | +| meshConfig.rootNamespace | string | `nil` | The namespace to treat as the administrative root namespace for Istio configuration. When processing a leaf namespace Istio will search for declarations in that namespace first and if none are found it will search in the root namespace. Any matching declaration found in the root namespace is processed as if it were declared in the leaf namespace. | +| meshConfig.trustDomain | string | `"cluster.local"` | The trust domain corresponds to the trust root of a system Refer to https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE-ID.md#21-trust-domain | +| pilot.autoscaleEnabled | bool | `false` | | +| pilot.autoscaleMax | int | `5` | | +| pilot.autoscaleMin | int | `1` | | +| pilot.configMap | bool | `true` | Install the mesh config map, generated from values.yaml. If false, pilot wil use default values (by default) or user-supplied values. | +| pilot.configSource | object | `{"subscribedResources":[]}` | This is used to set the source of configuration for the associated address in configSource, if nothing is specified the default MCP is assumed. | +| pilot.cpu.targetAverageUtilization | int | `80` | | +| pilot.deploymentLabels | object | `{}` | Additional labels to apply to the deployment. | +| pilot.enableProtocolSniffingForInbound | bool | `true` | if protocol sniffing is enabled for inbound | +| pilot.enableProtocolSniffingForOutbound | bool | `true` | if protocol sniffing is enabled for outbound | +| pilot.env.PILOT_ENABLE_CROSS_CLUSTER_WORKLOAD_ENTRY | string | `"false"` | | +| pilot.env.PILOT_ENABLE_METADATA_EXCHANGE | string | `"false"` | | +| pilot.env.PILOT_SCOPE_GATEWAY_TO_NAMESPACE | string | `"false"` | | +| pilot.env.VALIDATION_ENABLED | string | `"false"` | | +| pilot.hub | string | `"higress-registry.cn-hangzhou.cr.aliyuncs.com/higress"` | | +| pilot.image | string | `"pilot"` | Can be a full hub/image:tag | +| pilot.jwksResolverExtraRootCA | string | `""` | You can use jwksResolverExtraRootCA to provide a root certificate in PEM format. This will then be trusted by pilot when resolving JWKS URIs. | +| pilot.keepaliveMaxServerConnectionAge | string | `"30m"` | The following is used to limit how long a sidecar can be connected to a pilot. It balances out load across pilot instances at the cost of increasing system churn. | +| pilot.nodeSelector | object | `{}` | | +| pilot.plugins | list | `[]` | | +| pilot.podAnnotations | object | `{}` | | +| pilot.podLabels | object | `{}` | Additional labels to apply on the pod level for monitoring and logging configuration. | +| pilot.replicaCount | int | `1` | | +| pilot.resources | object | `{"requests":{"cpu":"500m","memory":"2048Mi"}}` | Resources for a small pilot install | +| pilot.rollingMaxSurge | string | `"100%"` | | +| pilot.rollingMaxUnavailable | string | `"25%"` | | +| pilot.serviceAnnotations | object | `{}` | | +| pilot.tag | string | `""` | | +| pilot.traceSampling | float | `1` | | +| revision | string | `""` | | +| tracing.enable | bool | `false` | | +| tracing.sampling | int | `100` | | +| tracing.skywalking.port | int | `11800` | | +| tracing.skywalking.service | string | `""` | | +| tracing.timeout | int | `500` | | +| upstream | object | `{"connectionBufferLimits":10485760,"idleTimeout":10}` | Upstream config settings | \ No newline at end of file diff --git a/helm/higress/README.md.gotmpl b/helm/higress/README.md.gotmpl new file mode 100644 index 0000000000..4fbd8dd226 --- /dev/null +++ b/helm/higress/README.md.gotmpl @@ -0,0 +1,34 @@ +## Higress for Kubernetes + +Higress is a cloud-native api gateway based on Alibaba's internal gateway practices. + +Powered by Istio and 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. + +## Setup Repo Info + +```console +helm repo add higress.io https://higress.io/helm-charts +helm repo update +``` + +## Install + +To install the chart with the release name `higress`: + +```console +helm install higress -n higress-system higress.io/higress --create-namespace --render-subchart-notes +``` + +## Uninstall + +To uninstall/delete the higress deployment: + +```console +helm delete higress -n higress-system +``` + +The command removes all the Kubernetes components associated with the chart and deletes the release. + +## Parameters + +{{ template "chart.valuesSection" . }} \ No newline at end of file From 2cb8558cda1570ff71edba403d462e95451d52c7 Mon Sep 17 00:00:00 2001 From: rinfx <893383980@qq.com> Date: Mon, 11 Nov 2024 14:49:17 +0800 Subject: [PATCH 58/64] Optimize AI security guard plugin (#1473) Co-authored-by: Kent Dong --- .../extensions/ai-security-guard/README.md | 30 +- .../extensions/ai-security-guard/go.mod | 2 +- .../extensions/ai-security-guard/go.sum | 4 +- .../extensions/ai-security-guard/main.go | 411 ++++++++++-------- 4 files changed, 244 insertions(+), 203 deletions(-) diff --git a/plugins/wasm-go/extensions/ai-security-guard/README.md b/plugins/wasm-go/extensions/ai-security-guard/README.md index 122d9e3677..68eeeae202 100644 --- a/plugins/wasm-go/extensions/ai-security-guard/README.md +++ b/plugins/wasm-go/extensions/ai-security-guard/README.md @@ -30,19 +30,23 @@ description: 阿里云内容安全检测 | `denyCode` | int | optional | 200 | 指定内容非法时的响应状态码 | | `denyMessage` | string | optional | openai格式的流式/非流式响应 | 指定内容非法时的响应内容 | | `protocol` | string | optional | openai | 协议格式,非openai协议填`original` | - -补充说明一下 `denyMessage`,对于openai格式的请求,对非法请求的处理逻辑为: -- 如果配置了 `denyMessage` - - 优先返回阿里云内容安全的建议回答,格式为openai格式的流式/非流式响应 - - 如果阿里云内容安全未返回建议的回答,返回内容为 `denyMessage` 配置内容,格式为openai格式的流式/非流式响应 -- 如果没有配置 `denyMessage` - - 优先返回阿里云内容安全的建议回答,格式为openai格式的流式/非流式响应 - - 如果阿里云内容安全未返回建议的回答,返回内容为内置的兜底回答,内容为`"很抱歉,我无法回答您的问题"`,格式为openai格式的流式/非流式响应 - -如果用户使用了非openai格式的协议,应当配置 `denyMessage`,此时对非法请求的处理逻辑为: -- 返回用户配置的 `denyMessage` 内容,用户可以配置其为序列化后的json字符串,以保持与正常请求接口返回格式的一致性 -- 如果 `denyMessage` 为空,优先返回阿里云内容安全的建议回答,格式为纯文本 -- 如果阿里云内容安全未返回建议回答,返回内置的兜底回答,内容为`"很抱歉,我无法回答您的问题"`,格式为纯文本 +| `riskLevelBar` | string | optional | high | 拦截风险等级,取值为 max, high, medium, low | + +补充说明一下 `denyMessage`,对非法请求的处理逻辑为: +- 如果配置了 `denyMessage`,返回内容为 `denyMessage` 配置内容,格式为openai格式的流式/非流式响应 +- 如果没有配置 `denyMessage`,优先返回阿里云内容安全的建议回答,格式为openai格式的流式/非流式响应 +- 如果阿里云内容安全未返回建议的回答,返回内容为内置的兜底回答,内容为`"很抱歉,我无法回答您的问题"`,格式为openai格式的流式/非流式响应 + +如果用户使用了非openai格式的协议,此时对非法请求的处理逻辑为: +- 如果配置了 `denyMessage`,返回用户配置的 `denyMessage` 内容,非流式响应 +- 如果没有配置 `denyMessage`,优先返回阿里云内容安全的建议回答,非流式响应 +- 如果阿里云内容安全未返回建议回答,返回内置的兜底回答,内容为`"很抱歉,我无法回答您的问题"`,非流式响应 + +补充说明一下 `riskLevelBar` 的四个等级: +- `max`: 检测请求/响应内容,但是不会产生拦截行为 +- `high`: 内容安全检测结果中风险等级为 `high` 时产生拦截 +- `medium`: 内容安全检测结果中风险等级 >= `medium` 时产生拦截 +- `low`: 内容安全检测结果中风险等级 >= `low` 时产生拦截 ## 配置示例 ### 前提条件 diff --git a/plugins/wasm-go/extensions/ai-security-guard/go.mod b/plugins/wasm-go/extensions/ai-security-guard/go.mod index f2bc5a1436..3ab8ec1836 100644 --- a/plugins/wasm-go/extensions/ai-security-guard/go.mod +++ b/plugins/wasm-go/extensions/ai-security-guard/go.mod @@ -7,7 +7,7 @@ replace github.com/alibaba/higress/plugins/wasm-go => ../.. require ( github.com/alibaba/higress/plugins/wasm-go v1.3.6-0.20240522012622-fc6a6aad8906 github.com/higress-group/proxy-wasm-go-sdk v0.0.0-20240711023527-ba358c48772f - github.com/tidwall/gjson v1.14.3 + github.com/tidwall/gjson v1.17.3 ) require ( diff --git a/plugins/wasm-go/extensions/ai-security-guard/go.sum b/plugins/wasm-go/extensions/ai-security-guard/go.sum index f473e12b2d..042eae70f2 100644 --- a/plugins/wasm-go/extensions/ai-security-guard/go.sum +++ b/plugins/wasm-go/extensions/ai-security-guard/go.sum @@ -9,8 +9,8 @@ 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/gjson v1.17.3 h1:bwWLZU7icoKRG+C+0PNwIKC6FCJO/Q3p2pZvuP0jN94= +github.com/tidwall/gjson v1.17.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= diff --git a/plugins/wasm-go/extensions/ai-security-guard/main.go b/plugins/wasm-go/extensions/ai-security-guard/main.go index 5b61589616..cf7cdead07 100644 --- a/plugins/wasm-go/extensions/ai-security-guard/main.go +++ b/plugins/wasm-go/extensions/ai-security-guard/main.go @@ -35,12 +35,16 @@ func main() { } const ( - 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"}]}` - OpenAIStreamResponseFormat = OpenAIStreamResponseChunk + "\n\n" + OpenAIStreamResponseEnd + "\n\n" + `data: [DONE]` + MaxRisk = "max" + HighRisk = "high" + MediumRisk = "medium" + LowRisk = "low" + NoRisk = "none" - TracingPrefix = "trace_span_tag." + 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"}]}` + OpenAIStreamResponseFormat = OpenAIStreamResponseChunk + "\n\n" + OpenAIStreamResponseEnd + "\n\n" + `data: [DONE]` DefaultRequestCheckService = "llm_query_moderation" DefaultResponseCheckService = "llm_response_moderation" @@ -53,10 +57,37 @@ const ( AliyunUserAgent = "CIPFrom/AIGateway" ) +type Response struct { + Code int `json:"Code"` + Message string `json:"Message"` + RequestId string `json:"RequestId"` + Data Data `json:"Data"` +} + +type Data struct { + RiskLevel string `json:"RiskLevel"` + Result []Result `json:"Result,omitempty"` + Advice []Advice `json:"Advice,omitempty"` +} + +type Result struct { + RiskWords string `json:"RiskWords,omitempty"` + Description string `json:"Description,omitempty"` + Confidence float64 `json:"Confidence,omitempty"` + Label string `json:"Label,omitempty"` +} + +type Advice struct { + Answer string `json:"Answer,omitempty"` + HitLabel string `json:"HitLabel,omitempty"` + HitLibName string `json:"HitLibName,omitempty"` +} + type AISecurityConfig struct { client wrapper.HttpClient ak string sk string + token string checkRequest bool requestCheckService string requestContentJsonPath string @@ -67,6 +98,7 @@ type AISecurityConfig struct { denyCode int64 denyMessage string protocolOriginal bool + riskLevelBar string metrics map[string]proxywasm.MetricCounter } @@ -79,12 +111,31 @@ func (config *AISecurityConfig) incrementCounter(metricName string, inc uint64) counter.Increment(inc) } +func riskLevelToInt(riskLevel string) int { + switch riskLevel { + case MaxRisk: + return 4 + case HighRisk: + return 3 + case MediumRisk: + return 2 + case LowRisk: + return 1 + case NoRisk: + return 0 + default: + return -1 + } +} + func urlEncoding(rawStr string) string { encodedStr := url.PathEscape(rawStr) encodedStr = strings.ReplaceAll(encodedStr, "+", "%2B") encodedStr = strings.ReplaceAll(encodedStr, ":", "%3A") encodedStr = strings.ReplaceAll(encodedStr, "=", "%3D") encodedStr = strings.ReplaceAll(encodedStr, "&", "%26") + encodedStr = strings.ReplaceAll(encodedStr, "$", "%24") + encodedStr = strings.ReplaceAll(encodedStr, "@", "%40") return encodedStr } @@ -130,6 +181,7 @@ func parseConfig(json gjson.Result, config *AISecurityConfig, log wrapper.Log) e if config.ak == "" || config.sk == "" { return errors.New("invalid AK/SK config") } + config.token = json.Get("securityToken").String() config.checkRequest = json.Get("checkRequest").Bool() config.checkResponse = json.Get("checkResponse").Bool() config.protocolOriginal = json.Get("protocol").String() == "original" @@ -164,6 +216,14 @@ func parseConfig(json gjson.Result, config *AISecurityConfig, log wrapper.Log) e } else { config.responseStreamContentJsonPath = DefaultStreamingResponseJsonPath } + if obj := json.Get("riskLevelBar"); obj.Exists() { + config.riskLevelBar = obj.String() + if riskLevelToInt(config.riskLevelBar) <= 0 { + return errors.New("invalid risk level, value must be one of [max, high, medium, low]") + } + } else { + config.riskLevelBar = HighRisk + } config.client = wrapper.NewClusterClient(wrapper.FQDNCluster{ FQDN: serviceName, Port: servicePort, @@ -192,105 +252,82 @@ func onHttpRequestHeaders(ctx wrapper.HttpContext, config AISecurityConfig, log func onHttpRequestBody(ctx wrapper.HttpContext, config AISecurityConfig, body []byte, log wrapper.Log) types.Action { log.Debugf("checking request body...") - content := gjson.GetBytes(body, config.requestContentJsonPath).Raw - model := gjson.GetBytes(body, "model").Raw + 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 { - timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z") - randomID, _ := generateHexID(16) - params := map[string]string{ - "Format": "JSON", - "Version": "2022-03-02", - "SignatureMethod": "Hmac-SHA1", - "SignatureNonce": randomID, - "SignatureVersion": "1.0", - "Action": "TextModerationPlus", - "AccessKeyId": config.ak, - "Timestamp": timestamp, - "Service": config.requestCheckService, - "ServiceParameters": fmt.Sprintf(`{"content": %s}`, content), - } - signature := getSign(params, config.sk+"&") - reqParams := url.Values{} - for k, v := range params { - reqParams.Add(k, v) - } - reqParams.Add("Signature", signature) - err := config.client.Post(fmt.Sprintf("/?%s", reqParams.Encode()), [][2]string{{"User-Agent", AliyunUserAgent}}, nil, - func(statusCode int, responseHeaders http.Header, responseBody []byte) { - if statusCode != 200 { - log.Error(string(responseBody)) - proxywasm.ResumeHttpRequest() - return - } - respData := gjson.GetBytes(responseBody, "Data") - if respData.Exists() { - respAdvice := respData.Get("Advice") - respResult := respData.Get("Result") - var denyMessage string - messageNeedSerialization := true - if config.protocolOriginal { - // not openai - if config.denyMessage != "" { - denyMessage = config.denyMessage - } else if respAdvice.Exists() { - denyMessage = respAdvice.Array()[0].Get("Answer").Raw - messageNeedSerialization = false - } else { - denyMessage = DefaultDenyMessage - } - } else { - // openai - if respAdvice.Exists() { - denyMessage = respAdvice.Array()[0].Get("Answer").Raw - messageNeedSerialization = false - } else if config.denyMessage != "" { - denyMessage = config.denyMessage - } else { - denyMessage = DefaultDenyMessage - } - } - if messageNeedSerialization { - if data, err := json.Marshal(denyMessage); err == nil { - denyMessage = string(data) - } else { - denyMessage = fmt.Sprintf("\"%s\"", DefaultDenyMessage) - } - } - if respResult.Array()[0].Get("Label").String() != "nonLabel" { - proxywasm.SetProperty([]string{TracingPrefix, "ai_sec_risklabel"}, []byte(respResult.Array()[0].Get("Label").String())) - proxywasm.SetProperty([]string{TracingPrefix, "ai_sec_deny_phase"}, []byte("request")) - config.incrementCounter("ai_sec_request_deny", 1) - if config.protocolOriginal { - proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "application/json"}}, []byte(denyMessage), -1) - } else if gjson.GetBytes(body, "stream").Bool() { - randomID := generateRandomID() - jsonData := []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, denyMessage, randomID, model)) - 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, denyMessage)) - proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1) - } - ctx.DontReadResponseBody() - } else { - proxywasm.ResumeHttpRequest() - } - } else { - proxywasm.ResumeHttpRequest() - } - }, - ) - if err != nil { - log.Errorf("failed call the safe check service: %v", err) - return types.ActionContinue - } - return types.ActionPause - } else { - log.Debugf("request content is empty. skip") + if len(content) == 0 { + log.Info("request content is empty. skip") + return types.ActionContinue + } + timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z") + randomID, _ := generateHexID(16) + params := map[string]string{ + "Format": "JSON", + "Version": "2022-03-02", + "SignatureMethod": "Hmac-SHA1", + "SignatureNonce": randomID, + "SignatureVersion": "1.0", + "Action": "TextModerationPlus", + "AccessKeyId": config.ak, + "Timestamp": timestamp, + "Service": config.requestCheckService, + "ServiceParameters": fmt.Sprintf(`{"content": "%s"}`, marshalStr(content, log)), + } + if config.token != "" { + params["SecurityToken"] = config.token + } + signature := getSign(params, config.sk+"&") + reqParams := url.Values{} + for k, v := range params { + reqParams.Add(k, v) + } + reqParams.Add("Signature", signature) + err := config.client.Post(fmt.Sprintf("/?%s", reqParams.Encode()), [][2]string{{"User-Agent", AliyunUserAgent}}, nil, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + log.Info(string(responseBody)) + if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 { + proxywasm.ResumeHttpRequest() + return + } + var response Response + err := json.Unmarshal(responseBody, &response) + if err != nil { + log.Error("failed to unmarshal aliyun content security response at request phase") + proxywasm.ResumeHttpRequest() + return + } + if riskLevelToInt(response.Data.RiskLevel) < riskLevelToInt(config.riskLevelBar) { + proxywasm.ResumeHttpRequest() + return + } + denyMessage := DefaultDenyMessage + if config.denyMessage != "" { + denyMessage = config.denyMessage + } else if response.Data.Advice != nil && response.Data.Advice[0].Answer != "" { + denyMessage = response.Data.Advice[0].Answer + } + marshalledDenyMessage := marshalStr(denyMessage, log) + if config.protocolOriginal { + 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)) + 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)) + proxywasm.SendHttpResponse(uint32(config.denyCode), [][2]string{{"content-type", "application/json"}}, jsonData, -1) + } + ctx.DontReadResponseBody() + config.incrementCounter("ai_sec_request_deny", 1) + }, + ) + if err != nil { + log.Errorf("failed call the safe check service: %v", err) return types.ActionContinue } + return types.ActionPause } func convertHeaders(hs [][2]string) map[string][]string { @@ -341,92 +378,81 @@ func onHttpResponseBody(ctx wrapper.HttpContext, config AISecurityConfig, body [ if isStreamingResponse { content = extractMessageFromStreamingBody(body, config.responseStreamContentJsonPath) } else { - content = gjson.GetBytes(body, config.responseContentJsonPath).Raw + content = gjson.GetBytes(body, config.responseContentJsonPath).String() } log.Debugf("Raw response content is: %s", content) - if len(content) > 0 { - timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z") - randomID, _ := generateHexID(16) - params := map[string]string{ - "Format": "JSON", - "Version": "2022-03-02", - "SignatureMethod": "Hmac-SHA1", - "SignatureNonce": randomID, - "SignatureVersion": "1.0", - "Action": "TextModerationPlus", - "AccessKeyId": config.ak, - "Timestamp": timestamp, - "Service": config.responseCheckService, - "ServiceParameters": fmt.Sprintf(`{"content": %s}`, content), - } - signature := getSign(params, config.sk+"&") - reqParams := url.Values{} - for k, v := range params { - reqParams.Add(k, v) - } - reqParams.Add("Signature", signature) - err := config.client.Post(fmt.Sprintf("/?%s", reqParams.Encode()), [][2]string{{"User-Agent", AliyunUserAgent}}, nil, - func(statusCode int, responseHeaders http.Header, responseBody []byte) { - defer proxywasm.ResumeHttpResponse() - if statusCode != 200 { - log.Error(string(responseBody)) - return - } - respData := gjson.GetBytes(responseBody, "Data") - if respData.Exists() { - respAdvice := respData.Get("Advice") - respResult := respData.Get("Result") - var denyMessage string - if config.protocolOriginal { - // not openai - if config.denyMessage != "" { - denyMessage = config.denyMessage - } else if respAdvice.Exists() { - denyMessage = respAdvice.Array()[0].Get("Answer").Raw - } else { - denyMessage = DefaultDenyMessage - } - } else { - // openai - if respAdvice.Exists() { - denyMessage = respAdvice.Array()[0].Get("Answer").Raw - } else if config.denyMessage != "" { - denyMessage = config.denyMessage - } else { - denyMessage = DefaultDenyMessage - } - } - if respResult.Array()[0].Get("Label").String() != "nonLabel" { - var jsonData []byte - if config.protocolOriginal { - jsonData = []byte(denyMessage) - } else if isStreamingResponse { - randomID := generateRandomID() - jsonData = []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, denyMessage, randomID, model)) - } else { - randomID := generateRandomID() - jsonData = []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, model, denyMessage)) - } - delete(hdsMap, "content-length") - hdsMap[":status"] = []string{fmt.Sprint(config.denyCode)} - proxywasm.ReplaceHttpResponseHeaders(reconvertHeaders(hdsMap)) - proxywasm.ReplaceHttpResponseBody(jsonData) - proxywasm.SetProperty([]string{TracingPrefix, "ai_sec_risklabel"}, []byte(respResult.Array()[0].Get("Label").String())) - proxywasm.SetProperty([]string{TracingPrefix, "ai_sec_deny_phase"}, []byte("response")) - config.incrementCounter("ai_sec_response_deny", 1) - } - } - }, - ) - if err != nil { - log.Errorf("failed call the safe check service: %v", err) - return types.ActionContinue - } - return types.ActionPause - } else { - log.Debugf("request content is empty. skip") + if len(content) == 0 { + log.Info("response content is empty. skip") return types.ActionContinue } + timestamp := time.Now().UTC().Format("2006-01-02T15:04:05Z") + randomID, _ := generateHexID(16) + params := map[string]string{ + "Format": "JSON", + "Version": "2022-03-02", + "SignatureMethod": "Hmac-SHA1", + "SignatureNonce": randomID, + "SignatureVersion": "1.0", + "Action": "TextModerationPlus", + "AccessKeyId": config.ak, + "Timestamp": timestamp, + "Service": config.responseCheckService, + "ServiceParameters": fmt.Sprintf(`{"content": "%s"}`, marshalStr(content, log)), + } + if config.token != "" { + params["SecurityToken"] = config.token + } + signature := getSign(params, config.sk+"&") + reqParams := url.Values{} + for k, v := range params { + reqParams.Add(k, v) + } + reqParams.Add("Signature", signature) + err := config.client.Post(fmt.Sprintf("/?%s", reqParams.Encode()), [][2]string{{"User-Agent", AliyunUserAgent}}, nil, + func(statusCode int, responseHeaders http.Header, responseBody []byte) { + defer proxywasm.ResumeHttpResponse() + log.Info(string(responseBody)) + if statusCode != 200 || gjson.GetBytes(responseBody, "Code").Int() != 200 { + return + } + var response Response + err := json.Unmarshal(responseBody, &response) + if err != nil { + log.Error("failed to unmarshal aliyun content security response at response phase") + return + } + if riskLevelToInt(response.Data.RiskLevel) < riskLevelToInt(config.riskLevelBar) { + return + } + denyMessage := DefaultDenyMessage + if config.denyMessage != "" { + denyMessage = config.denyMessage + } else if response.Data.Advice != nil && response.Data.Advice[0].Answer != "" { + denyMessage = response.Data.Advice[0].Answer + } + marshalledDenyMessage := marshalStr(denyMessage, log) + var jsonData []byte + if config.protocolOriginal { + jsonData = []byte(marshalledDenyMessage) + } else if isStreamingResponse { + randomID := generateRandomID() + jsonData = []byte(fmt.Sprintf(OpenAIStreamResponseFormat, randomID, model, marshalledDenyMessage, randomID, model)) + } else { + randomID := generateRandomID() + jsonData = []byte(fmt.Sprintf(OpenAIResponseFormat, randomID, model, marshalledDenyMessage)) + } + delete(hdsMap, "content-length") + hdsMap[":status"] = []string{fmt.Sprint(config.denyCode)} + proxywasm.ReplaceHttpResponseHeaders(reconvertHeaders(hdsMap)) + proxywasm.ReplaceHttpResponseBody(jsonData) + config.incrementCounter("ai_sec_response_deny", 1) + }, + ) + if err != nil { + log.Errorf("failed call the safe check service: %v", err) + return types.ActionContinue + } + return types.ActionPause } func extractMessageFromStreamingBody(data []byte, jsonPath string) string { @@ -434,10 +460,21 @@ func extractMessageFromStreamingBody(data []byte, jsonPath string) string { strChunks := []string{} for _, chunk := range chunks { // Example: "choices":[{"index":0,"delta":{"role":"assistant","content":"%s"},"logprobs":null,"finish_reason":null}] - jsonRaw := gjson.GetBytes(chunk, jsonPath).Raw - if len(jsonRaw) > 2 { - strChunks = append(strChunks, jsonRaw[1:len(jsonRaw)-1]) - } + strChunks = append(strChunks, gjson.GetBytes(chunk, jsonPath).String()) + } + return strings.Join(strChunks, "") +} + +func marshalStr(raw string, log wrapper.Log) string { + helper := map[string]string{ + "placeholder": raw, + } + marshalledHelper, _ := json.Marshal(helper) + marshalledRaw := gjson.GetBytes(marshalledHelper, "placeholder").Raw + if len(marshalledRaw) >= 2 { + return marshalledRaw[1 : len(marshalledRaw)-1] + } else { + log.Errorf("failed to marshal json string, raw string is: %s", raw) + return "" } - return fmt.Sprintf(`"%s"`, strings.Join(strChunks, "")) } From 60e476da873cb5df81cda5fc4d6308c9a6d6d496 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Mon, 11 Nov 2024 17:47:26 +0800 Subject: [PATCH 59/64] fix example sse build error (#1503) --- .gitignore | 3 +- plugins/wasm-rust/Cargo.lock | 392 ------- .../wasm-rust/example/sse-timing/Cargo.lock | 270 ----- .../wasm-rust/example/sse-timing/src/lib.rs | 2 +- .../extensions/ai-data-masking/Cargo.lock | 1010 ----------------- .../wasm-rust/extensions/demo-wasm/Cargo.lock | 405 ------- .../extensions/request-block/Cargo.lock | 442 -------- .../wasm-rust/extensions/say-hello/Cargo.lock | 402 ------- 8 files changed, 3 insertions(+), 2923 deletions(-) delete mode 100644 plugins/wasm-rust/Cargo.lock delete mode 100644 plugins/wasm-rust/example/sse-timing/Cargo.lock delete mode 100644 plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock delete mode 100644 plugins/wasm-rust/extensions/demo-wasm/Cargo.lock delete mode 100644 plugins/wasm-rust/extensions/request-block/Cargo.lock delete mode 100644 plugins/wasm-rust/extensions/say-hello/Cargo.lock diff --git a/.gitignore b/.gitignore index 8779853659..f3960e77c6 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ helm/**/charts/**.tgz target/ tools/hack/cluster.conf envoy/1.20 -istio/1.12 \ No newline at end of file +istio/1.12 +Cargo.lock diff --git a/plugins/wasm-rust/Cargo.lock b/plugins/wasm-rust/Cargo.lock deleted file mode 100644 index 63272f9a61..0000000000 --- a/plugins/wasm-rust/Cargo.lock +++ /dev/null @@ -1,392 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "higress-wasm-rust" -version = "0.1.0" -dependencies = [ - "downcast-rs", - "http", - "lazy_static", - "multimap", - "proxy-wasm", - "redis", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.161" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -dependencies = [ - "serde", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "proc-macro2" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxy-wasm" -version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" -dependencies = [ - "downcast-rs", - "hashbrown", - "log", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redis" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" -dependencies = [ - "arc-swap", - "combine", - "itoa", - "num-bigint", - "percent-encoding", - "ryu", - "url", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "serde" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "2.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/plugins/wasm-rust/example/sse-timing/Cargo.lock b/plugins/wasm-rust/example/sse-timing/Cargo.lock deleted file mode 100644 index 9123a0e01c..0000000000 --- a/plugins/wasm-rust/example/sse-timing/Cargo.lock +++ /dev/null @@ -1,270 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "bytes" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "428d9aa8fbc0670b7b8d6030a7fadd0f86151cae55e4dbbece15f3780a3dfaf3" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "higress-wasm-rust" -version = "0.1.0" -dependencies = [ - "downcast-rs", - "http", - "lazy_static", - "multimap", - "proxy-wasm", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.161" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -dependencies = [ - "serde", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "proc-macro2" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxy-wasm" -version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#6735737fad486c8a7cc324241f58df4a160e7887" -dependencies = [ - "downcast-rs", - "hashbrown", - "log", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "serde" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.210" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "sse-timing" -version = "0.1.0" -dependencies = [ - "higress-wasm-rust", - "proxy-wasm", - "serde", - "serde_json", -] - -[[package]] -name = "syn" -version = "2.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/plugins/wasm-rust/example/sse-timing/src/lib.rs b/plugins/wasm-rust/example/sse-timing/src/lib.rs index fbff5daf9e..c4f4e6d9e5 100644 --- a/plugins/wasm-rust/example/sse-timing/src/lib.rs +++ b/plugins/wasm-rust/example/sse-timing/src/lib.rs @@ -75,7 +75,7 @@ impl RootContext for SseTimingRoot { rule_matcher: self.rule_matcher.clone(), vendor: "higress".into(), is_event_stream: false, - event_stream: EventStream::new(), + event_stream: EventStream::default(), start_time: self.get_current_time(), })) } diff --git a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock b/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock deleted file mode 100644 index e635ae454f..0000000000 --- a/plugins/wasm-rust/extensions/ai-data-masking/Cargo.lock +++ /dev/null @@ -1,1010 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "ai-data-masking" -version = "0.1.0" -dependencies = [ - "fancy-regex", - "grok", - "higress-wasm-rust", - "jieba-rs", - "jsonpath-rust", - "lazy_static", - "md5", - "proxy-wasm", - "rust-embed", - "serde", - "serde_json", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bit-set" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" -dependencies = [ - "bit-vec", -] - -[[package]] -name = "bit-vec" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "block-buffer" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" -dependencies = [ - "generic-array", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" - -[[package]] -name = "cc" -version = "1.1.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f" -dependencies = [ - "shlex", -] - -[[package]] -name = "cedarwood" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d910bedd62c24733263d0bed247460853c9d22e8956bd4cd964302095e04e90" -dependencies = [ - "smallvec", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "cpufeatures" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" -dependencies = [ - "libc", -] - -[[package]] -name = "crypto-common" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" -dependencies = [ - "generic-array", - "typenum", -] - -[[package]] -name = "darling" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn", -] - -[[package]] -name = "darling_macro" -version = "0.20.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" -dependencies = [ - "darling_core", - "quote", - "syn", -] - -[[package]] -name = "derive_builder" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" -dependencies = [ - "derive_builder_macro", -] - -[[package]] -name = "derive_builder_core" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "derive_builder_macro" -version = "0.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" -dependencies = [ - "derive_builder_core", - "syn", -] - -[[package]] -name = "digest" -version = "0.10.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" -dependencies = [ - "block-buffer", - "crypto-common", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "fancy-regex" -version = "0.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531e46835a22af56d1e3b66f04844bed63158bc094a628bec1d321d9b4c44bf2" -dependencies = [ - "bit-set", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "fxhash" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" -dependencies = [ - "byteorder", -] - -[[package]] -name = "generic-array" -version = "0.14.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" -dependencies = [ - "typenum", - "version_check", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "grok" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "273797968160270573071022613fc4aa28b91fe68f3eef6c96a1b2a1947ddfbd" -dependencies = [ - "glob", - "onig", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "higress-wasm-rust" -version = "0.1.0" -dependencies = [ - "downcast-rs", - "http", - "lazy_static", - "multimap", - "proxy-wasm", - "redis", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jieba-rs" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1e2b0210dc78b49337af9e49d7ae41a39dceac6e5985613f1cf7763e2f76a25" -dependencies = [ - "cedarwood", - "derive_builder", - "fxhash", - "lazy_static", - "phf", - "phf_codegen", - "regex", -] - -[[package]] -name = "jsonpath-rust" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "514f8a353ad9e85443b30fefe169ce93794ec7c98054a4312ab08530f15b7bb3" -dependencies = [ - "pest", - "pest_derive", - "regex", - "serde_json", - "thiserror", -] - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.161" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "md5" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -dependencies = [ - "serde", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "onig" -version = "6.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c4b31c8722ad9171c6d77d3557db078cab2bd50afcc9d09c8b315c59df8ca4f" -dependencies = [ - "bitflags", - "libc", - "once_cell", - "onig_sys", -] - -[[package]] -name = "onig_sys" -version = "69.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b829e3d7e9cc74c7e315ee8edb185bf4190da5acde74afd7fc59c35b1f086e7" -dependencies = [ - "cc", - "pkg-config", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pest" -version = "2.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879952a81a83930934cbf1786752d6dedc3b1f29e8f8fb2ad1d0a36f377cf442" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d214365f632b123a47fd913301e14c946c61d1c183ee245fa76eb752e59a02dd" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb55586734301717aea2ac313f50b2eb8f60d2fc3dc01d190eefa2e625f60c4e" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.7.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b75da2a70cf4d9cb76833c990ac9cd3923c9a8905a8929789ce347c84564d03d" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - -[[package]] -name = "phf" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" -dependencies = [ - "phf_shared", -] - -[[package]] -name = "phf_codegen" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" -dependencies = [ - "phf_generator", - "phf_shared", -] - -[[package]] -name = "phf_generator" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" -dependencies = [ - "phf_shared", - "rand", -] - -[[package]] -name = "phf_shared" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" -dependencies = [ - "siphasher", -] - -[[package]] -name = "pkg-config" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" - -[[package]] -name = "proc-macro2" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxy-wasm" -version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" -dependencies = [ - "downcast-rs", - "hashbrown", - "log", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" - -[[package]] -name = "redis" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" -dependencies = [ - "arc-swap", - "combine", - "itoa", - "num-bigint", - "percent-encoding", - "ryu", - "url", -] - -[[package]] -name = "regex" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rust-embed" -version = "8.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" -dependencies = [ - "rust-embed-impl", - "rust-embed-utils", - "walkdir", -] - -[[package]] -name = "rust-embed-impl" -version = "8.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" -dependencies = [ - "proc-macro2", - "quote", - "rust-embed-utils", - "syn", - "walkdir", -] - -[[package]] -name = "rust-embed-utils" -version = "8.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" -dependencies = [ - "sha2", - "walkdir", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "serde" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "sha2" -version = "0.10.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" -dependencies = [ - "cfg-if", - "cpufeatures", - "digest", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "siphasher" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "strsim" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" - -[[package]] -name = "syn" -version = "2.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "thiserror" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.64" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "typenum" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" - -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "walkdir" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock b/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock deleted file mode 100644 index 1c6c135617..0000000000 --- a/plugins/wasm-rust/extensions/demo-wasm/Cargo.lock +++ /dev/null @@ -1,405 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "demo-wasm" -version = "0.1.0" -dependencies = [ - "higress-wasm-rust", - "http", - "multimap", - "proxy-wasm", - "redis", - "serde", - "serde_json", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "higress-wasm-rust" -version = "0.1.0" -dependencies = [ - "downcast-rs", - "http", - "lazy_static", - "multimap", - "proxy-wasm", - "redis", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.161" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -dependencies = [ - "serde", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "proc-macro2" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxy-wasm" -version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" -dependencies = [ - "downcast-rs", - "hashbrown", - "log", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redis" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" -dependencies = [ - "arc-swap", - "combine", - "itoa", - "num-bigint", - "percent-encoding", - "ryu", - "url", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "serde" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "2.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/plugins/wasm-rust/extensions/request-block/Cargo.lock b/plugins/wasm-rust/extensions/request-block/Cargo.lock deleted file mode 100644 index 36c0b812b7..0000000000 --- a/plugins/wasm-rust/extensions/request-block/Cargo.lock +++ /dev/null @@ -1,442 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "higress-wasm-rust" -version = "0.1.0" -dependencies = [ - "downcast-rs", - "http", - "lazy_static", - "multimap", - "proxy-wasm", - "redis", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.161" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -dependencies = [ - "serde", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "proc-macro2" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxy-wasm" -version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" -dependencies = [ - "downcast-rs", - "hashbrown", - "log", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redis" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" -dependencies = [ - "arc-swap", - "combine", - "itoa", - "num-bigint", - "percent-encoding", - "ryu", - "url", -] - -[[package]] -name = "regex" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38200e5ee88914975b69f657f0801b6f6dccafd44fd9326302a4aaeecfacb1d8" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "request-block" -version = "0.1.0" -dependencies = [ - "higress-wasm-rust", - "multimap", - "proxy-wasm", - "regex", - "serde", - "serde_json", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "serde" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "2.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] diff --git a/plugins/wasm-rust/extensions/say-hello/Cargo.lock b/plugins/wasm-rust/extensions/say-hello/Cargo.lock deleted file mode 100644 index 758fc617e3..0000000000 --- a/plugins/wasm-rust/extensions/say-hello/Cargo.lock +++ /dev/null @@ -1,402 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "ahash" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" -dependencies = [ - "cfg-if", - "once_cell", - "version_check", - "zerocopy", -] - -[[package]] -name = "allocator-api2" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bytes" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "combine" -version = "4.6.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" -dependencies = [ - "bytes", - "memchr", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "form_urlencoded" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" -dependencies = [ - "percent-encoding", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" -dependencies = [ - "ahash", - "allocator-api2", -] - -[[package]] -name = "higress-wasm-rust" -version = "0.1.0" -dependencies = [ - "downcast-rs", - "http", - "lazy_static", - "multimap", - "proxy-wasm", - "redis", - "serde", - "serde_json", - "uuid", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "idna" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" -dependencies = [ - "unicode-bidi", - "unicode-normalization", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.161" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1" - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "multimap" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "defc4c55412d89136f966bbb339008b474350e5e6e78d2714439c386b3137a03" -dependencies = [ - "serde", -] - -[[package]] -name = "num-bigint" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" -dependencies = [ - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "proc-macro2" -version = "1.0.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c3a7fc5db1e57d5a779a352c8cdb57b29aa4c40cc69c3a68a7fedc815fbf2f9" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "proxy-wasm" -version = "0.2.2" -source = "git+https://github.com/higress-group/proxy-wasm-rust-sdk?branch=main#8c902102091698bec953471c850bdf9799bc344d" -dependencies = [ - "downcast-rs", - "hashbrown", - "log", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "redis" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81cccf17a692ce51b86564334614d72dcae1def0fd5ecebc9f02956da74352b5" -dependencies = [ - "arc-swap", - "combine", - "itoa", - "num-bigint", - "percent-encoding", - "ryu", - "url", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "say-hello" -version = "0.1.0" -dependencies = [ - "higress-wasm-rust", - "proxy-wasm", - "serde", - "serde_json", -] - -[[package]] -name = "serde" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac55e59090389fb9f0dd9e0f3c09615afed1d19094284d0b200441f13550793" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.211" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be4f245ce16bc58d57ef2716271d0d4519e0f6defa147f6e081005bcb278ff" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "syn" -version = "2.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tinyvec" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" -dependencies = [ - "tinyvec_macros", -] - -[[package]] -name = "tinyvec_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "unicode-bidi" -version = "0.3.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-normalization" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] - -[[package]] -name = "url" -version = "2.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" -dependencies = [ - "form_urlencoded", - "idna", - "percent-encoding", -] - -[[package]] -name = "uuid" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" -dependencies = [ - "getrandom", -] - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] From 6a1bf90d42f02682b3b1d9f29478cd1bb9ef49e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=BA=AA=E5=8D=93=E5=BF=97?= Date: Tue, 12 Nov 2024 13:45:28 +0800 Subject: [PATCH 60/64] feat: supports custom prepare build script (#1490) --- plugins/wasm-rust/Dockerfile | 3 ++- plugins/wasm-rust/DockerfileBuilder | 5 +---- plugins/wasm-rust/extensions/ai-data-masking/.buildrc | 1 + 3 files changed, 4 insertions(+), 5 deletions(-) create mode 100644 plugins/wasm-rust/extensions/ai-data-masking/.buildrc diff --git a/plugins/wasm-rust/Dockerfile b/plugins/wasm-rust/Dockerfile index df55a5ad1d..1dce74bffe 100644 --- a/plugins/wasm-rust/Dockerfile +++ b/plugins/wasm-rust/Dockerfile @@ -1,11 +1,12 @@ FROM rust:1.80 as builder WORKDIR /workspace -RUN apt update && apt-get install gcc gcc-multilib llvm clang -y && apt clean RUN rustup target add wasm32-wasi ARG PLUGIN_NAME="say-hello" ARG BUILD_OPTS="--release" +ARG BUILDRC=".buildrc" COPY . . WORKDIR /workspace/extensions/$PLUGIN_NAME +RUN if [ -f $BUILDRC ]; then sh $BUILDRC; fi RUN cargo build --target wasm32-wasi $BUILD_OPTS \ && cp target/wasm32-wasi/release/*.wasm /main.wasm diff --git a/plugins/wasm-rust/DockerfileBuilder b/plugins/wasm-rust/DockerfileBuilder index 3f9879af3b..d05add1f2b 100644 --- a/plugins/wasm-rust/DockerfileBuilder +++ b/plugins/wasm-rust/DockerfileBuilder @@ -8,10 +8,7 @@ FROM $BASE_IMAGE LABEL rust_version=$RUST_VERSION oras_version=$ORAS_VERSION -RUN apt-get update \ - && apt-get install -y wget gcc gcc-multilib llvm clang \ - && rustup target add wasm32-wasi \ - && rm -rf /var/lib/apt/lists/* +RUN rustup target add wasm32-wasi RUN arch="$(dpkg --print-architecture)"; arch="${arch##*-}"; \ rust_version=${RUST_VERSION:-1.82}; \ diff --git a/plugins/wasm-rust/extensions/ai-data-masking/.buildrc b/plugins/wasm-rust/extensions/ai-data-masking/.buildrc new file mode 100644 index 0000000000..bd317b8605 --- /dev/null +++ b/plugins/wasm-rust/extensions/ai-data-masking/.buildrc @@ -0,0 +1 @@ +apt update && apt-get install gcc gcc-multilib llvm clang -y && apt clean \ No newline at end of file From a787e237ceb5792c3f0b7575b9ce161291c9c4c1 Mon Sep 17 00:00:00 2001 From: hanans426 <32064961+hanans426@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:26:55 +0800 Subject: [PATCH 61/64] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=BF=AB=E9=80=9F?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E5=88=B0=E9=98=BF=E9=87=8C=E4=BA=91=E7=9A=84?= =?UTF-8?q?=E9=83=A8=E7=BD=B2=E6=96=B9=E6=A1=88=20(#1506)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 9ae48a4577..fd15371c6b 100644 --- a/README.md +++ b/README.md @@ -64,6 +64,10 @@ docker run -d --rm --name higress-ai -v ${PWD}:/data \ K8s 下使用 Helm 部署等其他安装方式可以参考官网 [Quick Start 文档](https://higress.cn/docs/latest/user/quickstart/)。 +如果您是在云上部署,生产环境推荐使用[企业版](https://higress.io/cloud/),开发测试可以使用下面一键部署社区版: + +[![Deploy on AlibabaCloud ComputeNest](https://service-info-public.oss-cn-hangzhou.aliyuncs.com/computenest.svg)](https://computenest.console.aliyun.com/service/instance/create/default?type=user&ServiceName=Higress社区版) + ## 使用场景 From ca97cbd75a6d4296d27e2ccb37a277fada89b2d2 Mon Sep 17 00:00:00 2001 From: 007gzs <007gzs@gmail.com> Date: Wed, 13 Nov 2024 17:39:24 +0800 Subject: [PATCH 62/64] fix workflows build-and-push-wasm-plugin-image (#1508) --- .github/workflows/build-and-push-wasm-plugin-image.yaml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-and-push-wasm-plugin-image.yaml b/.github/workflows/build-and-push-wasm-plugin-image.yaml index ef6de46c8d..13b8521238 100644 --- a/.github/workflows/build-and-push-wasm-plugin-image.yaml +++ b/.github/workflows/build-and-push-wasm-plugin-image.yaml @@ -42,17 +42,19 @@ jobs: plugin_type="${{ github.event.inputs.plugin_type }}" plugin_name="${{ github.event.inputs.plugin_name }}" version="${{ github.event.inputs.version }}" - builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-rust-builder:rust${{ env.RUST_VERSION }}-oras${{ env.ORAS_VERSION }}" else ref_name=${{ github.ref_name }} plugin_type=${ref_name#*-} # 删除插件类型前面的字段(wasm-) - plugin_type=${plugin_type%-*} # 删除插件类型后面的字段(-{plugin_name}-vX.Y.Z) + plugin_type=${plugin_type%%-*} # 删除插件类型后面的字段(-{plugin_name}-vX.Y.Z) plugin_name=${ref_name#*-*-} # 删除插件名前面的字段(wasm-go-) plugin_name=${plugin_name%-*} # 删除插件名后面的字段(-vX.Y.Z) version=$(echo "$ref_name" | awk -F'v' '{print $2}') + fi + if [[ "$plugin_type" == "rust" ]]; then + builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-rust-builder:rust${{ env.RUST_VERSION }}-oras${{ env.ORAS_VERSION }}" + else builder_image="higress-registry.cn-hangzhou.cr.aliyuncs.com/plugins/wasm-go-builder:go${{ env.GO_VERSION }}-tinygo${{ env.TINYGO_VERSION }}-oras${{ env.ORAS_VERSION }}" fi - echo "PLUGIN_TYPE=$plugin_type" >> $GITHUB_ENV echo "PLUGIN_NAME=$plugin_name" >> $GITHUB_ENV echo "VERSION=$version" >> $GITHUB_ENV From ebc5b2987ea0d0d2cffda2b1df92a2ed7eaafeb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 14 Nov 2024 18:49:21 +0800 Subject: [PATCH 63/64] fix compile of wasm cpp plugins (#1511) --- istio/istio | 2 +- plugins/wasm-cpp/WORKSPACE | 12 ++-- plugins/wasm-cpp/bazel/absl.patch | 16 ++--- plugins/wasm-cpp/bazel/wasm.bzl | 10 +-- plugins/wasm-cpp/common/http_util.cc | 65 +++++++++++++++++-- plugins/wasm-cpp/common/http_util.h | 17 ++++- plugins/wasm-cpp/extensions/bot_detect/BUILD | 5 +- .../wasm-cpp/extensions/custom_response/BUILD | 5 +- plugins/wasm-cpp/extensions/hmac_auth/BUILD | 5 +- plugins/wasm-cpp/extensions/jwt_auth/BUILD | 3 +- plugins/wasm-cpp/extensions/key_auth/BUILD | 5 +- .../wasm-cpp/extensions/key_rate_limit/BUILD | 5 +- .../wasm-cpp/extensions/model_router/BUILD | 4 +- .../wasm-cpp/extensions/request_block/BUILD | 5 +- .../wasm-cpp/extensions/sni_misdirect/BUILD | 5 +- 15 files changed, 116 insertions(+), 48 deletions(-) diff --git a/istio/istio b/istio/istio index ce6a5d5934..1dbd773596 160000 --- a/istio/istio +++ b/istio/istio @@ -1 +1 @@ -Subproject commit ce6a5d59348fca11729cfcd6aa016aba9f8cd784 +Subproject commit 1dbd77359624ab4af2953a4840927fbd7ea1d668 diff --git a/plugins/wasm-cpp/WORKSPACE b/plugins/wasm-cpp/WORKSPACE index ed78d0df06..fe0a7223c4 100644 --- a/plugins/wasm-cpp/WORKSPACE +++ b/plugins/wasm-cpp/WORKSPACE @@ -27,13 +27,17 @@ http_archive( url = "https://github.com/higress-group/proxy-wasm-cpp-sdk/archive/" + PROXY_WASM_CPP_SDK_SHA + ".tar.gz", ) -load("@proxy_wasm_cpp_sdk//bazel/dep:deps.bzl", "wasm_dependencies") +load("@proxy_wasm_cpp_sdk//bazel:repositories.bzl", "proxy_wasm_cpp_sdk_repositories") -wasm_dependencies() +proxy_wasm_cpp_sdk_repositories() -load("@proxy_wasm_cpp_sdk//bazel/dep:deps_extra.bzl", "wasm_dependencies_extra") +load("@proxy_wasm_cpp_sdk//bazel:dependencies.bzl", "proxy_wasm_cpp_sdk_dependencies") -wasm_dependencies_extra() +proxy_wasm_cpp_sdk_dependencies() + +load("@proxy_wasm_cpp_sdk//bazel:dependencies_extra.bzl", "proxy_wasm_cpp_sdk_dependencies_extra") + +proxy_wasm_cpp_sdk_dependencies_extra() load("@istio_ecosystem_wasm_extensions//bazel:wasm.bzl", "wasm_libraries") diff --git a/plugins/wasm-cpp/bazel/absl.patch b/plugins/wasm-cpp/bazel/absl.patch index 68c66d37dc..be952f8e3c 100644 --- a/plugins/wasm-cpp/bazel/absl.patch +++ b/plugins/wasm-cpp/bazel/absl.patch @@ -2,16 +2,16 @@ diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/interna index d8cb047..0c5f182 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc -@@ -18,6 +18,8 @@ - #endif - #endif +@@ -12,6 +12,8 @@ + // See the License for the specific language governing permissions and + // limitations under the License. +#define HAS_STRPTIME 0 + - #if defined(HAS_STRPTIME) && HAS_STRPTIME - #if !defined(_XOPEN_SOURCE) - #define _XOPEN_SOURCE // Definedness suffices for strptime. -@@ -58,7 +60,7 @@ namespace { + #if !defined(HAS_STRPTIME) + #if !defined(_MSC_VER) && !defined(__MINGW32__) + #define HAS_STRPTIME 1 // assume everyone has strptime() except windows +@@ -58,7 +60,7 @@ #if !HAS_STRPTIME // Build a strptime() using C++11's std::get_time(). @@ -20,7 +20,7 @@ index d8cb047..0c5f182 100644 std::istringstream input(s); input >> std::get_time(tm, fmt); if (input.fail()) return nullptr; -@@ -648,7 +650,7 @@ const char* ParseSubSeconds(const char* dp, detail::femtoseconds* subseconds) { +@@ -648,7 +650,7 @@ // Parses a string into a std::tm using strptime(3). const char* ParseTM(const char* dp, const char* fmt, std::tm* tm) { if (dp != nullptr) { diff --git a/plugins/wasm-cpp/bazel/wasm.bzl b/plugins/wasm-cpp/bazel/wasm.bzl index 1f061fbc69..b0e5973937 100644 --- a/plugins/wasm-cpp/bazel/wasm.bzl +++ b/plugins/wasm-cpp/bazel/wasm.bzl @@ -9,9 +9,9 @@ load( def wasm_libraries(): http_archive( name = "com_google_absl", - sha256 = "ec8ef47335310cc3382bdc0d0cc1097a001e67dc83fcba807845aa5696e7e1e4", - strip_prefix = "abseil-cpp-302b250e1d917ede77b5ff00a6fd9f28430f1563", - url = "https://github.com/abseil/abseil-cpp/archive/302b250e1d917ede77b5ff00a6fd9f28430f1563.tar.gz", + sha256 = "3a0bb3d2e6f53352526a8d1a7e7b5749c68cd07f2401766a404fb00d2853fa49", + strip_prefix = "abseil-cpp-4bbdb026899fea9f882a95cbd7d6a4adaf49b2dd", + url = "https://github.com/abseil/abseil-cpp/archive/4bbdb026899fea9f882a95cbd7d6a4adaf49b2dd.tar.gz", patch_args = ["-p1"], patches = ["//bazel:absl.patch"], ) @@ -33,8 +33,8 @@ def wasm_libraries(): urls = ["https://github.com/google/googletest/archive/release-1.10.0.tar.gz"], ) - PROXY_WASM_CPP_HOST_SHA = "7850d1721fe3dd2ccfb86a06116f76c23b1f1bf8" - PROXY_WASM_CPP_HOST_SHA256 = "740690fc1d749849f6e24b5bc48a07dabc0565a7d03b6cd13425dba693956c57" + PROXY_WASM_CPP_HOST_SHA = "ecf42a27fcf78f42e64037d4eff1a0ca5a61e403" + PROXY_WASM_CPP_HOST_SHA256 = "9748156731e9521837686923321bf12725c32c9fa8355218209831cc3ee87080" http_archive( name = "proxy_wasm_cpp_host", diff --git a/plugins/wasm-cpp/common/http_util.cc b/plugins/wasm-cpp/common/http_util.cc index 320ed916c5..0d671ac61c 100644 --- a/plugins/wasm-cpp/common/http_util.cc +++ b/plugins/wasm-cpp/common/http_util.cc @@ -19,7 +19,6 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" - #include "common/common_util.h" namespace Wasm::Common::Http { @@ -190,7 +189,8 @@ std::vector getAllOfHeader(std::string_view key) { std::vector result; auto headers = getRequestHeaderPairs()->pairs(); for (auto& header : headers) { - if (absl::EqualsIgnoreCase(Wasm::Common::stdToAbsl(header.first), Wasm::Common::stdToAbsl(key))) { + if (absl::EqualsIgnoreCase(Wasm::Common::stdToAbsl(header.first), + Wasm::Common::stdToAbsl(key))) { result.push_back(std::string(header.second)); } } @@ -225,7 +225,8 @@ void forEachCookie( v = v.substr(1, v.size() - 2); } - if (!cookie_consumer(Wasm::Common::abslToStd(k), Wasm::Common::abslToStd(v))) { + if (!cookie_consumer(Wasm::Common::abslToStd(k), + Wasm::Common::abslToStd(v))) { return; } } @@ -265,7 +266,63 @@ std::string buildOriginalUri(std::optional max_path_length) { auto scheme = scheme_ptr->view(); auto host_ptr = getRequestHeader(Header::Host); auto host = host_ptr->view(); - return absl::StrCat(Wasm::Common::stdToAbsl(scheme), "://", Wasm::Common::stdToAbsl(host), Wasm::Common::stdToAbsl(final_path)); + return absl::StrCat(Wasm::Common::stdToAbsl(scheme), "://", + Wasm::Common::stdToAbsl(host), + Wasm::Common::stdToAbsl(final_path)); +} + +void extractHostPathFromUri(const absl::string_view& uri, + absl::string_view& host, absl::string_view& path) { + /** + * URI RFC: https://www.ietf.org/rfc/rfc2396.txt + * + * Example: + * uri = "https://example.com:8443/certs" + * pos: ^ + * host_pos: ^ + * path_pos: ^ + * host = "example.com:8443" + * path = "/certs" + */ + const auto pos = uri.find("://"); + // Start position of the host + const auto host_pos = (pos == std::string::npos) ? 0 : pos + 3; + // Start position of the path + const auto path_pos = uri.find('/', host_pos); + if (path_pos == std::string::npos) { + // If uri doesn't have "/", the whole string is treated as host. + host = uri.substr(host_pos); + path = "/"; + } else { + host = uri.substr(host_pos, path_pos - host_pos); + path = uri.substr(path_pos); + } +} + +void extractPathWithoutArgsFromUri(const std::string_view& uri, + std::string_view& path_without_args) { + auto params_pos = uri.find('?'); + size_t uri_end; + if (params_pos == std::string::npos) { + uri_end = uri.size(); + } else { + uri_end = params_pos; + } + path_without_args = uri.substr(0, uri_end); +} + +bool hasRequestBody() { + auto contentType = getRequestHeader("content-type")->toString(); + auto contentLengthStr = getRequestHeader("content-length")->toString(); + auto transferEncoding = getRequestHeader("transfer-encoding")->toString(); + + if (!contentType.empty()) { + return true; + } + if (!contentLengthStr.empty()) { + return true; + } + return transferEncoding.find("chunked") != std::string::npos; } } // namespace Wasm::Common::Http diff --git a/plugins/wasm-cpp/common/http_util.h b/plugins/wasm-cpp/common/http_util.h index fcc01f6ff8..da8877f032 100644 --- a/plugins/wasm-cpp/common/http_util.h +++ b/plugins/wasm-cpp/common/http_util.h @@ -42,6 +42,12 @@ namespace Wasm::Common::Http { using QueryParams = std::map; using SystemTime = std::chrono::time_point; +namespace Status { +constexpr int OK = 200; +constexpr int InternalServerError = 500; +constexpr int Unauthorized = 401; +} // namespace Status + namespace Header { constexpr std::string_view Scheme(":scheme"); constexpr std::string_view Method(":method"); @@ -52,14 +58,17 @@ constexpr std::string_view Accept("accept"); constexpr std::string_view ContentMD5("content-md5"); constexpr std::string_view ContentType("content-type"); constexpr std::string_view ContentLength("content-length"); +constexpr std::string_view TransferEncoding("transfer-encoding"); constexpr std::string_view UserAgent("user-agent"); constexpr std::string_view Date("date"); constexpr std::string_view Cookie("cookie"); +constexpr std::string_view StrictTransportSecurity("strict-transport-security"); } // namespace Header namespace ContentTypeValues { constexpr std::string_view Grpc{"application/grpc"}; -} +constexpr std::string_view Json{"application/json"}; +} // namespace ContentTypeValues class PercentEncoding { public: @@ -142,4 +151,10 @@ std::unordered_map parseCookies( std::string buildOriginalUri(std::optional max_path_length); +void extractHostPathFromUri(const absl::string_view& uri, + absl::string_view& host, absl::string_view& path); + +void extractPathWithoutArgsFromUri(const std::string_view& uri, + std::string_view& path_without_args); +bool hasRequestBody(); } // namespace Wasm::Common::Http diff --git a/plugins/wasm-cpp/extensions/bot_detect/BUILD b/plugins/wasm-cpp/extensions/bot_detect/BUILD index e5ba81c26b..808c34cd40 100644 --- a/plugins/wasm-cpp/extensions/bot_detect/BUILD +++ b/plugins/wasm-cpp/extensions/bot_detect/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "bot_detect.wasm", srcs = [ "plugin.cc", @@ -28,7 +28,6 @@ wasm_cc_binary( "//common:http_util", "//common:regex_util", "//common:rule_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) diff --git a/plugins/wasm-cpp/extensions/custom_response/BUILD b/plugins/wasm-cpp/extensions/custom_response/BUILD index d6202d7b08..dd0d5a6de2 100644 --- a/plugins/wasm-cpp/extensions/custom_response/BUILD +++ b/plugins/wasm-cpp/extensions/custom_response/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "custom_response.wasm", srcs = [ "plugin.cc", @@ -27,7 +27,6 @@ wasm_cc_binary( "//common:json_util", "//common:http_util", "//common:rule_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) diff --git a/plugins/wasm-cpp/extensions/hmac_auth/BUILD b/plugins/wasm-cpp/extensions/hmac_auth/BUILD index 9fd9ad93b3..3f3dd1ed9d 100644 --- a/plugins/wasm-cpp/extensions/hmac_auth/BUILD +++ b/plugins/wasm-cpp/extensions/hmac_auth/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "hmac_auth.wasm", srcs = [ "plugin.cc", @@ -30,7 +30,6 @@ wasm_cc_binary( "//common:crypto_util", "//common:http_util", "//common:rule_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) diff --git a/plugins/wasm-cpp/extensions/jwt_auth/BUILD b/plugins/wasm-cpp/extensions/jwt_auth/BUILD index c960158a15..6d31fc857f 100644 --- a/plugins/wasm-cpp/extensions/jwt_auth/BUILD +++ b/plugins/wasm-cpp/extensions/jwt_auth/BUILD @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") wasm_cc_binary( @@ -33,7 +33,6 @@ wasm_cc_binary( "//common:json_util", "//common:http_util", "//common:rule_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) diff --git a/plugins/wasm-cpp/extensions/key_auth/BUILD b/plugins/wasm-cpp/extensions/key_auth/BUILD index 8668bc5fe8..5dc3ff4886 100644 --- a/plugins/wasm-cpp/extensions/key_auth/BUILD +++ b/plugins/wasm-cpp/extensions/key_auth/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "key_auth.wasm", srcs = [ "plugin.cc", @@ -28,7 +28,6 @@ wasm_cc_binary( "//common:json_util", "//common:http_util", "//common:rule_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) diff --git a/plugins/wasm-cpp/extensions/key_rate_limit/BUILD b/plugins/wasm-cpp/extensions/key_rate_limit/BUILD index 7f4cd92495..19cbfa3724 100644 --- a/plugins/wasm-cpp/extensions/key_rate_limit/BUILD +++ b/plugins/wasm-cpp/extensions/key_rate_limit/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "key_rate_limit.wasm", srcs = [ "plugin.cc", @@ -29,7 +29,6 @@ wasm_cc_binary( "//common:json_util", "//common:http_util", "//common:rule_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) diff --git a/plugins/wasm-cpp/extensions/model_router/BUILD b/plugins/wasm-cpp/extensions/model_router/BUILD index 67cfa547db..ead7b8ebec 100644 --- a/plugins/wasm-cpp/extensions/model_router/BUILD +++ b/plugins/wasm-cpp/extensions/model_router/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "model_router.wasm", srcs = [ "plugin.cc", diff --git a/plugins/wasm-cpp/extensions/request_block/BUILD b/plugins/wasm-cpp/extensions/request_block/BUILD index 518209139b..f1eaa40d06 100644 --- a/plugins/wasm-cpp/extensions/request_block/BUILD +++ b/plugins/wasm-cpp/extensions/request_block/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "request_block.wasm", srcs = [ "plugin.cc", @@ -27,7 +27,6 @@ wasm_cc_binary( "//common:json_util", "//common:http_util", "//common:rule_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) diff --git a/plugins/wasm-cpp/extensions/sni_misdirect/BUILD b/plugins/wasm-cpp/extensions/sni_misdirect/BUILD index b4b32b4c10..5a24973e06 100644 --- a/plugins/wasm-cpp/extensions/sni_misdirect/BUILD +++ b/plugins/wasm-cpp/extensions/sni_misdirect/BUILD @@ -12,10 +12,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -load("@proxy_wasm_cpp_sdk//bazel/wasm:wasm.bzl", "wasm_cc_binary") +load("@proxy_wasm_cpp_sdk//bazel:defs.bzl", "proxy_wasm_cc_binary") load("//bazel:wasm.bzl", "declare_wasm_image_targets") -wasm_cc_binary( +proxy_wasm_cc_binary( name = "sni_misdirect.wasm", srcs = [ "plugin.cc", @@ -25,7 +25,6 @@ wasm_cc_binary( "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/strings", "//common:http_util", - "@proxy_wasm_cpp_sdk//:proxy_wasm_intrinsics", ], ) From f2a5df3949e83aabee9730d8dcc66f3a20799e1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=BE=84=E6=BD=AD?= Date: Thu, 14 Nov 2024 18:50:33 +0800 Subject: [PATCH 64/64] use the body returned by the ext auth server when auth fails (#1510) --- plugins/wasm-go/extensions/ext-auth/main.go | 8 ++++---- plugins/wasm-go/extensions/ext-auth/utils.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/plugins/wasm-go/extensions/ext-auth/main.go b/plugins/wasm-go/extensions/ext-auth/main.go index 3dc0556a00..7caf168341 100644 --- a/plugins/wasm-go/extensions/ext-auth/main.go +++ b/plugins/wasm-go/extensions/ext-auth/main.go @@ -120,7 +120,7 @@ func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, lo defer proxywasm.ResumeHttpRequest() if statusCode != http.StatusOK { log.Errorf("failed to call ext auth server, status: %d", statusCode) - callExtAuthServerErrorHandler(config, statusCode, responseHeaders) + callExtAuthServerErrorHandler(config, statusCode, responseHeaders, responseBody) return } @@ -137,13 +137,13 @@ func checkExtAuth(ctx wrapper.HttpContext, config ExtAuthConfig, body []byte, lo if err != nil { log.Errorf("failed to call ext auth server: %v", err) // Since the handling logic for call errors and HTTP status code 500 is the same, we directly use 500 here. - callExtAuthServerErrorHandler(config, http.StatusInternalServerError, nil) + callExtAuthServerErrorHandler(config, http.StatusInternalServerError, nil, nil) return types.ActionContinue } return pauseAction } -func callExtAuthServerErrorHandler(config ExtAuthConfig, statusCode int, extAuthRespHeaders http.Header) { +func callExtAuthServerErrorHandler(config ExtAuthConfig, statusCode int, extAuthRespHeaders http.Header, responseBody []byte) { if statusCode >= http.StatusInternalServerError && config.failureModeAllow { if config.failureModeAllowHeaderAdd { _ = proxywasm.ReplaceHttpRequestHeader(HeaderFailureModeAllow, "true") @@ -167,5 +167,5 @@ func callExtAuthServerErrorHandler(config ExtAuthConfig, statusCode int, extAuth if statusCode >= http.StatusInternalServerError { statusToUse = int(config.statusOnError) } - _ = sendResponse(uint32(statusToUse), "ext-auth.unauthorized", respHeaders) + _ = sendResponse(uint32(statusToUse), "ext-auth.unauthorized", respHeaders, responseBody) } diff --git a/plugins/wasm-go/extensions/ext-auth/utils.go b/plugins/wasm-go/extensions/ext-auth/utils.go index 42094f1c8b..6b97530334 100644 --- a/plugins/wasm-go/extensions/ext-auth/utils.go +++ b/plugins/wasm-go/extensions/ext-auth/utils.go @@ -8,8 +8,8 @@ import ( "github.com/higress-group/proxy-wasm-go-sdk/proxywasm" ) -func sendResponse(statusCode uint32, statusCodeDetailData string, headers http.Header) error { - return proxywasm.SendHttpResponseWithDetail(statusCode, statusCodeDetailData, reconvertHeaders(headers), nil, -1) +func sendResponse(statusCode uint32, statusCodeDetailData string, headers http.Header, body []byte) error { + return proxywasm.SendHttpResponseWithDetail(statusCode, statusCodeDetailData, reconvertHeaders(headers), body, -1) } func reconvertHeaders(headers http.Header) [][2]string {