From f2203cc04f11af12735524027c2253c888ad038e Mon Sep 17 00:00:00 2001 From: zhiyuan <136570416+gshilei@users.noreply.github.com> Date: Wed, 8 May 2024 14:36:19 +0800 Subject: [PATCH] repo-sync-2024-05-08T11:48:38+0800 (#31) --- .bazelversion | 2 +- .circleci/config.yml | 82 +++- .clang-format | 8 + .gitignore | 1 + BUILD | 2 + Makefile | 27 +- WORKSPACE | 11 + .../dockerfile/kuscia-envoy-anolis.Dockerfile | 7 +- .../api/filters/http/kuscia_poller/v3/BUILD | 11 + .../http/kuscia_poller/v3/poller.pb.go | 354 +++++++++++++++ .../kuscia_poller/v3/poller.pb.validate.go | 418 ++++++++++++++++++ .../http/kuscia_poller/v3/poller.proto | 41 ++ .../api/filters/http/kuscia_receiver/v3/BUILD | 11 + .../http/kuscia_receiver/v3/message.pb.go | 313 +++++++++++++ .../kuscia_receiver/v3/message.pb.validate.go | 254 +++++++++++ .../http/kuscia_receiver/v3/message.proto | 36 ++ .../http/kuscia_receiver/v3/receiver.pb.go | 260 +++++++++++ .../v3/receiver.pb.validate.go | 276 ++++++++++++ .../http/kuscia_receiver/v3/receiver.proto | 30 ++ .../source/filters/http/kuscia_common/BUILD | 10 +- .../filters/http/kuscia_common/coder.cc | 41 ++ .../source/filters/http/kuscia_common/coder.h | 36 ++ .../filters/http/kuscia_common/common.h | 31 +- .../filters/http/kuscia_common/framer.cc | 102 +++++ .../filters/http/kuscia_common/framer.h | 60 +++ .../http/kuscia_common/kuscia_header.h | 27 +- kuscia/source/filters/http/kuscia_gress/BUILD | 3 + .../filters/http/kuscia_gress/gress_filter.cc | 406 +++++++++-------- .../filters/http/kuscia_gress/gress_filter.h | 151 +++---- .../source/filters/http/kuscia_poller/BUILD | 47 ++ .../filters/http/kuscia_poller/callbacks.cc | 245 ++++++++++ .../filters/http/kuscia_poller/callbacks.h | 93 ++++ .../filters/http/kuscia_poller/common.h | 34 ++ .../filters/http/kuscia_poller/config.cc | 42 ++ .../filters/http/kuscia_poller/config.h | 43 ++ .../http/kuscia_poller/poller_filter.cc | 332 ++++++++++++++ .../http/kuscia_poller/poller_filter.h | 80 ++++ .../source/filters/http/kuscia_receiver/BUILD | 60 +++ .../filters/http/kuscia_receiver/config.cc | 39 ++ .../filters/http/kuscia_receiver/config.h | 40 ++ .../filters/http/kuscia_receiver/conn.h | 314 +++++++++++++ .../filters/http/kuscia_receiver/event.h | 213 +++++++++ .../filters/http/kuscia_receiver/event_loop.h | 331 ++++++++++++++ .../http/kuscia_receiver/receiver_filter.cc | 284 ++++++++++++ .../http/kuscia_receiver/receiver_filter.h | 99 +++++ .../http/kuscia_receiver/svc_register.h | 205 +++++++++ 46 files changed, 5202 insertions(+), 310 deletions(-) create mode 100644 .clang-format create mode 100755 kuscia/api/filters/http/kuscia_poller/v3/BUILD create mode 100644 kuscia/api/filters/http/kuscia_poller/v3/poller.pb.go create mode 100644 kuscia/api/filters/http/kuscia_poller/v3/poller.pb.validate.go create mode 100755 kuscia/api/filters/http/kuscia_poller/v3/poller.proto create mode 100644 kuscia/api/filters/http/kuscia_receiver/v3/BUILD create mode 100644 kuscia/api/filters/http/kuscia_receiver/v3/message.pb.go create mode 100644 kuscia/api/filters/http/kuscia_receiver/v3/message.pb.validate.go create mode 100644 kuscia/api/filters/http/kuscia_receiver/v3/message.proto create mode 100644 kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.go create mode 100644 kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.validate.go create mode 100644 kuscia/api/filters/http/kuscia_receiver/v3/receiver.proto create mode 100644 kuscia/source/filters/http/kuscia_common/coder.cc create mode 100644 kuscia/source/filters/http/kuscia_common/coder.h create mode 100644 kuscia/source/filters/http/kuscia_common/framer.cc create mode 100644 kuscia/source/filters/http/kuscia_common/framer.h create mode 100755 kuscia/source/filters/http/kuscia_poller/BUILD create mode 100644 kuscia/source/filters/http/kuscia_poller/callbacks.cc create mode 100644 kuscia/source/filters/http/kuscia_poller/callbacks.h create mode 100644 kuscia/source/filters/http/kuscia_poller/common.h create mode 100755 kuscia/source/filters/http/kuscia_poller/config.cc create mode 100755 kuscia/source/filters/http/kuscia_poller/config.h create mode 100644 kuscia/source/filters/http/kuscia_poller/poller_filter.cc create mode 100644 kuscia/source/filters/http/kuscia_poller/poller_filter.h create mode 100644 kuscia/source/filters/http/kuscia_receiver/BUILD create mode 100644 kuscia/source/filters/http/kuscia_receiver/config.cc create mode 100644 kuscia/source/filters/http/kuscia_receiver/config.h create mode 100644 kuscia/source/filters/http/kuscia_receiver/conn.h create mode 100644 kuscia/source/filters/http/kuscia_receiver/event.h create mode 100644 kuscia/source/filters/http/kuscia_receiver/event_loop.h create mode 100644 kuscia/source/filters/http/kuscia_receiver/receiver_filter.cc create mode 100644 kuscia/source/filters/http/kuscia_receiver/receiver_filter.h create mode 100644 kuscia/source/filters/http/kuscia_receiver/svc_register.h diff --git a/.bazelversion b/.bazelversion index f9da12e..f4965a3 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.3.2 \ No newline at end of file +6.0.0 \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 53030db..9db72d6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,28 +4,90 @@ version: 2.1 # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/configuration-reference/#jobs -jobs: - build-envoy: + +executors: + linux_x64_executor: # declares a reusable executor docker: - - image: secretflow/ubuntu-base-ci:latest + - image: envoyproxy/envoy-build-ubuntu:7304f974de2724617b7492ccb4c9c58cd420353a resource_class: 2xlarge shell: /bin/bash --login -eo pipefail + linux_aarch64_executor: + docker: + - image: envoyproxy/envoy-build-ubuntu:7304f974de2724617b7492ccb4c9c58cd420353a + resource_class: arm.2xlarge + shell: /bin/bash --login -eo pipefail + +commands: + build_envoy: steps: - - checkout - run: - name: Build envoy + name: "make build-envoy-local" command: | git submodule update --init git config --global --add safe.directory ./ - bazel build //:envoy -c opt --ui_event_filters=-info,-debug,-warning --jobs 16 - mkdir -p build_apps - mv bazel-bin/envoy build_apps + make build-envoy-local - store_artifacts: - path: build_apps + path: output + +jobs: + linux_build_envoy: + parameters: + executor: + type: string + executor: <> + steps: + - checkout + - build_envoy + docker_image_publish: + docker: + - image: cimg/deploy:2023.06.1 + steps: + - attach_workspace: + at: output + - checkout + - setup_remote_docker + - run: + name: Build Docker image + command: | + CIRCLETAG=$(echo ${CIRCLE_TAG} | sed 's/v//') + docker buildx create --name kuscia-envoy --platform linux/arm64,linux/amd64 --use + IMG=secretflow/kuscia-envoy + IMG_LATEST={IMG}:latest + IMG_TAG={IMG}:{CIRCLETAG} + + ALIYUN_IMG=secretflow-registry.cn-hangzhou.cr.aliyuncs.com/secretflow/kuscia-envoy + ALIYUN_IMG_LATEST={ALIYUN_IMG}:latest + ALIYUN_IMG_TAG={ALIYUN_IMG}:{CIRCLETAG} + + #login docker + docker login -u ${DOCKER_USERNAME} -p ${DOCKER_DEPLOY_TOKEN} + + docker buildx build -t ${IMG_LATEST} --platform linux/arm64,linux/amd64 -f ./build_image/dockerfile/kuscia-envoy-anolis.Dockerfile . --push + docker buildx build -t ${IMG_TAG} --platform linux/arm64,linux/amd64 -f ./build_image/dockerfile/kuscia-envoy-anolis.Dockerfile . --push + + + # login docker - aliyun + docker login -u ${ALIYUN_DOCKER_USERNAME} -p ${ALIYUN_DOCKER_PASSWORD} secretflow-registry.cn-hangzhou.cr.aliyuncs.com + docker buildx build -t {ALIYUN_IMG_LATEST} --platform linux/amd64,linux/arm64 -f ./build_image/dockerfile/kuscia-envoy-anolis.Dockerfile . --push + docker buildx build -t {ALIYUN_IMG_TAG} --platform linux/amd64,linux/arm64 -f ./build_image/dockerfile/kuscia-envoy-anolis.Dockerfile . --push + + + # Orchestrate jobs using workflows # See: https://circleci.com/docs/configuration-reference/#workflows workflows: build-workflow: jobs: - - build-envoy + - linux_build_envoy: + matrix: + parameters: + executor: [ "linux_x64_executor", "linux_aarch64_executor" ] + - docker_image_publish: + requires: + - linux_build_envoy + filters: + branches: + ignore: /.*/ + tags: + only: /^v.*/ \ No newline at end of file diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c8375e6 --- /dev/null +++ b/.clang-format @@ -0,0 +1,8 @@ +Language: Cpp +BasedOnStyle: LLVM +DerivePointerAlignment: false +PointerAlignment: Left +ColumnLimit: 99 +MaxEmptyLinesToKeep: 1 +PenaltyBreakAssignment: 2 +AlignTrailingComments: true diff --git a/.gitignore b/.gitignore index 2ce66db..38d4440 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ bazel-* .bazel-cache .idea .vscode +cache diff --git a/BUILD b/BUILD index d52664f..d0d58d6 100644 --- a/BUILD +++ b/BUILD @@ -13,6 +13,8 @@ envoy_cc_binary( "//kuscia/source/filters/http/kuscia_crypt:kuscia_crypt_config", "//kuscia/source/filters/http/kuscia_token_auth:kuscia_token_auth_config", "//kuscia/source/filters/http/kuscia_header_decorator:kuscia_header_decorator_config", + "//kuscia/source/filters/http/kuscia_poller:kuscia_poller_config", + "//kuscia/source/filters/http/kuscia_receiver:kuscia_receiver_config", "@envoy//source/exe:envoy_main_entry_lib", ], ) diff --git a/Makefile b/Makefile index c408ee1..967f71e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ SHELL := /bin/bash -BUILD_IMAGE = envoyproxy/envoy-build-ubuntu:81a93046060dbe5620d5b3aa92632090a9ee4da6 +BUILD_IMAGE = envoyproxy/envoy-build-ubuntu:7304f974de2724617b7492ccb4c9c58cd420353a # Image URL to use all building image targets DATETIME = $(shell date +"%Y%m%d%H%M%S") @@ -8,6 +8,12 @@ COMMIT_ID = $(shell git log -1 --pretty="format:%h") TAG = ${KUSCIA_VERSION_TAG}-${DATETIME}-${COMMIT_ID} IMG ?= secretflow/kuscia-envoy:${TAG} +# Get current architecture information +UNAME_M_OUTPUT := $(shell uname -m) + +# To configure the ARCH variable to either arm64 or amd64 or UNAME_M_OUTPUT +ARCH := $(if $(filter aarch64 arm64,$(UNAME_M_OUTPUT)),arm64,$(if $(filter amd64 x86_64,$(UNAME_M_OUTPUT)),amd64,$(UNAME_M_OUTPUT))) + CONTAINER_NAME ?= "build-envoy-$(shell echo ${USER})" COMPILE_MODE ?=opt TARGET ?= "//:envoy" @@ -17,6 +23,8 @@ TEST_COMPILE_MODE = fastbuild TEST_TARGET ?= "//kuscia/test/..." TEST_LOG_LEVEL = debug +GCC_VERSION := $(shell docker exec -it $(CONTAINER_NAME) /bin/bash -c 'gcc --version | grep gcc | head -n 1 | cut -d" " -f4') + define start_docker if [ ! -f "./envoy/BUILD" ]; then\ git submodule update --init;\ @@ -26,7 +34,13 @@ define start_docker -e GOPROXY='https://goproxy.cn,direct' --cap-add=NET_ADMIN $(BUILD_IMAGE);\ docker exec -it $(CONTAINER_NAME) /bin/bash -c 'git config --global --add safe.directory /home/admin/dev';\ fi; - + echo "GCC_VERSION: $(GCC_VERSION)";\ + if [[ ($(ARCH) == "aarch64" || $(ARCH) == "arm64") && $(GCC_VERSION) == "9.4.0" ]]; then\ + echo "ARCH: $(ARCH) - Install gcc-11 g++-11";\ + docker exec $(CONTAINER_NAME) /bin/bash -c 'apt update';\ + docker exec $(CONTAINER_NAME) /bin/bash -c 'apt install -y gcc-11 g++-11';\ + docker exec $(CONTAINER_NAME) /bin/bash -c 'rm /usr/bin/g++ /usr/bin/gcc && ln -s /usr/bin/g++-11 /usr/bin/g++ && ln -s /usr/bin/gcc-11 /usr/bin/gcc';\ + fi; endef define stop_docker @@ -40,14 +54,15 @@ build-envoy: @$(call start_docker) docker exec -it ${CONTAINER_NAME} make build-envoy-local docker exec -it ${CONTAINER_NAME} strip -s /home/admin/dev/bazel-bin/envoy - mkdir -p output/bin - mkdir -p output/conf - docker cp ${CONTAINER_NAME}:/home/admin/dev/bazel-bin/envoy output/bin - docker cp ${CONTAINER_NAME}:/home/admin/dev/kuscia/conf/envoy.yaml output/conf + .PHONY: build-envoy-local build-envoy-local: bazel build -c ${COMPILE_MODE} ${TARGET} --verbose_failures ${BUILD_OPTS} --@envoy//source/extensions/wasm_runtime/v8:enabled=false + mkdir -p output/linux/${ARCH}/bin + mkdir -p output/linux/${ARCH}/conf + cp bazel-bin/envoy output/linux/${ARCH}/bin + cp kuscia/conf/envoy.yaml output/linux/${ARCH}/conf .PHONY: test-envoy test-envoy: diff --git a/WORKSPACE b/WORKSPACE index 72a6f05..9f4c123 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -5,6 +5,17 @@ local_repository( path = "envoy", ) +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "bazel_gazelle", + sha256 = "501deb3d5695ab658e82f6f6f549ba681ea3ca2a5fb7911154b5aa45596183fa", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.26.0/bazel-gazelle-v0.26.0.tar.gz", + "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.26.0/bazel-gazelle-v0.26.0.tar.gz", + ], +) + load("@envoy//bazel:api_binding.bzl", "envoy_api_binding") envoy_api_binding() diff --git a/build_image/dockerfile/kuscia-envoy-anolis.Dockerfile b/build_image/dockerfile/kuscia-envoy-anolis.Dockerfile index 51667c6..c97bfaa 100644 --- a/build_image/dockerfile/kuscia-envoy-anolis.Dockerfile +++ b/build_image/dockerfile/kuscia-envoy-anolis.Dockerfile @@ -1,9 +1,12 @@ -FROM openanolis/anolisos:8.8 +FROM openanolis/anolisos:23 + +ARG TARGETPLATFORM ENV TZ=Asia/Shanghai ARG ROOT_DIR="/home/kuscia" -COPY output $ROOT_DIR/ + +COPY ./output/$TARGETPLATFORM $ROOT_DIR/ WORKDIR ${ROOT_DIR} diff --git a/kuscia/api/filters/http/kuscia_poller/v3/BUILD b/kuscia/api/filters/http/kuscia_poller/v3/BUILD new file mode 100755 index 0000000..c496ff4 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_poller/v3/BUILD @@ -0,0 +1,11 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/kuscia/api/filters/http/kuscia_poller/v3/poller.pb.go b/kuscia/api/filters/http/kuscia_poller/v3/poller.pb.go new file mode 100644 index 0000000..f5e4ca2 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_poller/v3/poller.pb.go @@ -0,0 +1,354 @@ +// Copyright 2024 Ant Group Co., 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. +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v3.20.3 +// source: poller.proto + +package v3 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Poller struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ReceiverServiceName string `protobuf:"bytes,1,opt,name=receiver_service_name,json=receiverServiceName,proto3" json:"receiver_service_name,omitempty"` + RequestTimeout int32 `protobuf:"varint,2,opt,name=request_timeout,json=requestTimeout,proto3" json:"request_timeout,omitempty"` + ResponseTimeout int32 `protobuf:"varint,3,opt,name=response_timeout,json=responseTimeout,proto3" json:"response_timeout,omitempty"` + // as a reverse proxy, header decorator filter append specified entries to the request headers. + // for example, you can assign a token for each source, and the upstream cluster use the token to + // authorize requests + AppendHeaders []*Poller_SourceHeader `protobuf:"bytes,4,rep,name=append_headers,json=appendHeaders,proto3" json:"append_headers,omitempty"` +} + +func (x *Poller) Reset() { + *x = Poller{} + if protoimpl.UnsafeEnabled { + mi := &file_poller_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Poller) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Poller) ProtoMessage() {} + +func (x *Poller) ProtoReflect() protoreflect.Message { + mi := &file_poller_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Poller.ProtoReflect.Descriptor instead. +func (*Poller) Descriptor() ([]byte, []int) { + return file_poller_proto_rawDescGZIP(), []int{0} +} + +func (x *Poller) GetReceiverServiceName() string { + if x != nil { + return x.ReceiverServiceName + } + return "" +} + +func (x *Poller) GetRequestTimeout() int32 { + if x != nil { + return x.RequestTimeout + } + return 0 +} + +func (x *Poller) GetResponseTimeout() int32 { + if x != nil { + return x.ResponseTimeout + } + return 0 +} + +func (x *Poller) GetAppendHeaders() []*Poller_SourceHeader { + if x != nil { + return x.AppendHeaders + } + return nil +} + +type Poller_HeaderEntry struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Value string `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` +} + +func (x *Poller_HeaderEntry) Reset() { + *x = Poller_HeaderEntry{} + if protoimpl.UnsafeEnabled { + mi := &file_poller_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Poller_HeaderEntry) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Poller_HeaderEntry) ProtoMessage() {} + +func (x *Poller_HeaderEntry) ProtoReflect() protoreflect.Message { + mi := &file_poller_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Poller_HeaderEntry.ProtoReflect.Descriptor instead. +func (*Poller_HeaderEntry) Descriptor() ([]byte, []int) { + return file_poller_proto_rawDescGZIP(), []int{0, 0} +} + +func (x *Poller_HeaderEntry) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *Poller_HeaderEntry) GetValue() string { + if x != nil { + return x.Value + } + return "" +} + +type Poller_SourceHeader struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + Headers []*Poller_HeaderEntry `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty"` +} + +func (x *Poller_SourceHeader) Reset() { + *x = Poller_SourceHeader{} + if protoimpl.UnsafeEnabled { + mi := &file_poller_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Poller_SourceHeader) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Poller_SourceHeader) ProtoMessage() {} + +func (x *Poller_SourceHeader) ProtoReflect() protoreflect.Message { + mi := &file_poller_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Poller_SourceHeader.ProtoReflect.Descriptor instead. +func (*Poller_SourceHeader) Descriptor() ([]byte, []int) { + return file_poller_proto_rawDescGZIP(), []int{0, 1} +} + +func (x *Poller_SourceHeader) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *Poller_SourceHeader) GetHeaders() []*Poller_HeaderEntry { + if x != nil { + return x.Headers + } + return nil +} + +var File_poller_proto protoreflect.FileDescriptor + +var file_poller_proto_rawDesc = []byte{ + 0x0a, 0x0c, 0x70, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x2e, + 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, + 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x6b, 0x75, + 0x73, 0x63, 0x69, 0x61, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x76, 0x33, 0x22, 0xba, + 0x03, 0x0a, 0x06, 0x50, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x15, 0x72, 0x65, 0x63, + 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x5f, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x13, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, + 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x27, 0x0a, + 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x54, + 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x6f, 0x75, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, + 0x52, 0x0f, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x54, 0x69, 0x6d, 0x65, 0x6f, 0x75, + 0x74, 0x12, 0x6a, 0x0a, 0x0e, 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x68, 0x65, 0x61, 0x64, + 0x65, 0x72, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x43, 0x2e, 0x65, 0x6e, 0x76, 0x6f, + 0x79, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x76, 0x33, 0x2e, 0x50, 0x6f, 0x6c, 0x6c, 0x65, + 0x72, 0x2e, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x52, 0x0d, + 0x61, 0x70, 0x70, 0x65, 0x6e, 0x64, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x1a, 0x35, 0x0a, + 0x0b, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, + 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, + 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x1a, 0x84, 0x01, 0x0a, 0x0c, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, + 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x5c, 0x0a, + 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x42, + 0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x6b, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x76, 0x33, 0x2e, + 0x50, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x42, 0x4d, 0x5a, 0x4b, 0x67, + 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, + 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2d, 0x65, 0x6e, 0x76, 0x6f, + 0x79, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x6c, + 0x74, 0x65, 0x72, 0x73, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, + 0x5f, 0x70, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x2f, 0x76, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x33, +} + +var ( + file_poller_proto_rawDescOnce sync.Once + file_poller_proto_rawDescData = file_poller_proto_rawDesc +) + +func file_poller_proto_rawDescGZIP() []byte { + file_poller_proto_rawDescOnce.Do(func() { + file_poller_proto_rawDescData = protoimpl.X.CompressGZIP(file_poller_proto_rawDescData) + }) + return file_poller_proto_rawDescData +} + +var file_poller_proto_msgTypes = make([]protoimpl.MessageInfo, 3) +var file_poller_proto_goTypes = []interface{}{ + (*Poller)(nil), // 0: envoy.extensions.filters.http.kuscia_poller.v3.Poller + (*Poller_HeaderEntry)(nil), // 1: envoy.extensions.filters.http.kuscia_poller.v3.Poller.HeaderEntry + (*Poller_SourceHeader)(nil), // 2: envoy.extensions.filters.http.kuscia_poller.v3.Poller.SourceHeader +} +var file_poller_proto_depIdxs = []int32{ + 2, // 0: envoy.extensions.filters.http.kuscia_poller.v3.Poller.append_headers:type_name -> envoy.extensions.filters.http.kuscia_poller.v3.Poller.SourceHeader + 1, // 1: envoy.extensions.filters.http.kuscia_poller.v3.Poller.SourceHeader.headers:type_name -> envoy.extensions.filters.http.kuscia_poller.v3.Poller.HeaderEntry + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_poller_proto_init() } +func file_poller_proto_init() { + if File_poller_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_poller_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Poller); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_poller_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Poller_HeaderEntry); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_poller_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Poller_SourceHeader); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_poller_proto_rawDesc, + NumEnums: 0, + NumMessages: 3, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_poller_proto_goTypes, + DependencyIndexes: file_poller_proto_depIdxs, + MessageInfos: file_poller_proto_msgTypes, + }.Build() + File_poller_proto = out.File + file_poller_proto_rawDesc = nil + file_poller_proto_goTypes = nil + file_poller_proto_depIdxs = nil +} diff --git a/kuscia/api/filters/http/kuscia_poller/v3/poller.pb.validate.go b/kuscia/api/filters/http/kuscia_poller/v3/poller.pb.validate.go new file mode 100644 index 0000000..bb7089f --- /dev/null +++ b/kuscia/api/filters/http/kuscia_poller/v3/poller.pb.validate.go @@ -0,0 +1,418 @@ +// Code generated by protoc-gen-validate. DO NOT EDIT. +// source: poller.proto + +package v3 + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/mail" + "net/url" + "regexp" + "sort" + "strings" + "time" + "unicode/utf8" + + "google.golang.org/protobuf/types/known/anypb" +) + +// ensure the imports are used +var ( + _ = bytes.MinRead + _ = errors.New("") + _ = fmt.Print + _ = utf8.UTFMax + _ = (*regexp.Regexp)(nil) + _ = (*strings.Reader)(nil) + _ = net.IPv4len + _ = time.Duration(0) + _ = (*url.URL)(nil) + _ = (*mail.Address)(nil) + _ = anypb.Any{} + _ = sort.Sort +) + +// Validate checks the field values on Poller with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *Poller) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on Poller with the rules defined in the +// proto definition for this message. If any rules are violated, the result is +// a list of violation errors wrapped in PollerMultiError, or nil if none found. +func (m *Poller) ValidateAll() error { + return m.validate(true) +} + +func (m *Poller) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for ReceiverServiceName + + // no validation rules for RequestTimeout + + // no validation rules for ResponseTimeout + + for idx, item := range m.GetAppendHeaders() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, PollerValidationError{ + field: fmt.Sprintf("AppendHeaders[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, PollerValidationError{ + field: fmt.Sprintf("AppendHeaders[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return PollerValidationError{ + field: fmt.Sprintf("AppendHeaders[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return PollerMultiError(errors) + } + + return nil +} + +// PollerMultiError is an error wrapping multiple validation errors returned by +// Poller.ValidateAll() if the designated constraints aren't met. +type PollerMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m PollerMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m PollerMultiError) AllErrors() []error { return m } + +// PollerValidationError is the validation error returned by Poller.Validate if +// the designated constraints aren't met. +type PollerValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e PollerValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e PollerValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e PollerValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e PollerValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e PollerValidationError) ErrorName() string { return "PollerValidationError" } + +// Error satisfies the builtin error interface +func (e PollerValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sPoller.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = PollerValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = PollerValidationError{} + +// Validate checks the field values on Poller_HeaderEntry with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *Poller_HeaderEntry) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on Poller_HeaderEntry with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// Poller_HeaderEntryMultiError, or nil if none found. +func (m *Poller_HeaderEntry) ValidateAll() error { + return m.validate(true) +} + +func (m *Poller_HeaderEntry) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Key + + // no validation rules for Value + + if len(errors) > 0 { + return Poller_HeaderEntryMultiError(errors) + } + + return nil +} + +// Poller_HeaderEntryMultiError is an error wrapping multiple validation errors +// returned by Poller_HeaderEntry.ValidateAll() if the designated constraints +// aren't met. +type Poller_HeaderEntryMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m Poller_HeaderEntryMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m Poller_HeaderEntryMultiError) AllErrors() []error { return m } + +// Poller_HeaderEntryValidationError is the validation error returned by +// Poller_HeaderEntry.Validate if the designated constraints aren't met. +type Poller_HeaderEntryValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e Poller_HeaderEntryValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e Poller_HeaderEntryValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e Poller_HeaderEntryValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e Poller_HeaderEntryValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e Poller_HeaderEntryValidationError) ErrorName() string { + return "Poller_HeaderEntryValidationError" +} + +// Error satisfies the builtin error interface +func (e Poller_HeaderEntryValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sPoller_HeaderEntry.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = Poller_HeaderEntryValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = Poller_HeaderEntryValidationError{} + +// Validate checks the field values on Poller_SourceHeader with the rules +// defined in the proto definition for this message. If any rules are +// violated, the first error encountered is returned, or nil if there are no violations. +func (m *Poller_SourceHeader) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on Poller_SourceHeader with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// Poller_SourceHeaderMultiError, or nil if none found. +func (m *Poller_SourceHeader) ValidateAll() error { + return m.validate(true) +} + +func (m *Poller_SourceHeader) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Source + + for idx, item := range m.GetHeaders() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, Poller_SourceHeaderValidationError{ + field: fmt.Sprintf("Headers[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, Poller_SourceHeaderValidationError{ + field: fmt.Sprintf("Headers[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return Poller_SourceHeaderValidationError{ + field: fmt.Sprintf("Headers[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return Poller_SourceHeaderMultiError(errors) + } + + return nil +} + +// Poller_SourceHeaderMultiError is an error wrapping multiple validation +// errors returned by Poller_SourceHeader.ValidateAll() if the designated +// constraints aren't met. +type Poller_SourceHeaderMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m Poller_SourceHeaderMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m Poller_SourceHeaderMultiError) AllErrors() []error { return m } + +// Poller_SourceHeaderValidationError is the validation error returned by +// Poller_SourceHeader.Validate if the designated constraints aren't met. +type Poller_SourceHeaderValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e Poller_SourceHeaderValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e Poller_SourceHeaderValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e Poller_SourceHeaderValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e Poller_SourceHeaderValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e Poller_SourceHeaderValidationError) ErrorName() string { + return "Poller_SourceHeaderValidationError" +} + +// Error satisfies the builtin error interface +func (e Poller_SourceHeaderValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sPoller_SourceHeader.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = Poller_SourceHeaderValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = Poller_SourceHeaderValidationError{} diff --git a/kuscia/api/filters/http/kuscia_poller/v3/poller.proto b/kuscia/api/filters/http/kuscia_poller/v3/poller.proto new file mode 100755 index 0000000..cab5ae5 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_poller/v3/poller.proto @@ -0,0 +1,41 @@ +// Copyright 2024 Ant Group Co., 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. +// + +syntax = "proto3"; + +package envoy.extensions.filters.http.kuscia_poller.v3; +option go_package = "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/kuscia_poller/v3"; + +message Poller { + string receiver_service_name = 1; + int32 request_timeout = 2; + int32 response_timeout = 3; + + message HeaderEntry { + string key = 1; + string value = 2; + } + message SourceHeader { + string source = 1; + repeated HeaderEntry headers = 2; + } + + // as a reverse proxy, header decorator filter append specified entries to the request headers. + // for example, you can assign a token for each source, and the upstream cluster use the token to + // authorize requests + repeated SourceHeader append_headers = 4; + + int32 heartbeat_interval = 5; +} diff --git a/kuscia/api/filters/http/kuscia_receiver/v3/BUILD b/kuscia/api/filters/http/kuscia_receiver/v3/BUILD new file mode 100644 index 0000000..c496ff4 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_receiver/v3/BUILD @@ -0,0 +1,11 @@ +# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py. + +load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package") + +licenses(["notice"]) # Apache 2 + +api_proto_package( + deps = [ + "@com_github_cncf_udpa//udpa/annotations:pkg", + ], +) diff --git a/kuscia/api/filters/http/kuscia_receiver/v3/message.pb.go b/kuscia/api/filters/http/kuscia_receiver/v3/message.pb.go new file mode 100644 index 0000000..70ab79f --- /dev/null +++ b/kuscia/api/filters/http/kuscia_receiver/v3/message.pb.go @@ -0,0 +1,313 @@ +// Copyright 2024 Ant Group Co., 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. +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v3.20.3 +// source: message.proto + +package v3 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type RequestMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + Host string `protobuf:"bytes,2,opt,name=host,proto3" json:"host,omitempty"` + Path string `protobuf:"bytes,3,opt,name=path,proto3" json:"path,omitempty"` + Method string `protobuf:"bytes,4,opt,name=method,proto3" json:"method,omitempty"` + Headers map[string]string `protobuf:"bytes,5,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Body []byte `protobuf:"bytes,6,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *RequestMessage) Reset() { + *x = RequestMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *RequestMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RequestMessage) ProtoMessage() {} + +func (x *RequestMessage) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RequestMessage.ProtoReflect.Descriptor instead. +func (*RequestMessage) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{0} +} + +func (x *RequestMessage) GetId() string { + if x != nil { + return x.Id + } + return "" +} + +func (x *RequestMessage) GetHost() string { + if x != nil { + return x.Host + } + return "" +} + +func (x *RequestMessage) GetPath() string { + if x != nil { + return x.Path + } + return "" +} + +func (x *RequestMessage) GetMethod() string { + if x != nil { + return x.Method + } + return "" +} + +func (x *RequestMessage) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +func (x *RequestMessage) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +type ResponseMessage struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + StatusCode int32 `protobuf:"varint,1,opt,name=status_code,json=statusCode,proto3" json:"status_code,omitempty"` + Headers map[string]string `protobuf:"bytes,2,rep,name=headers,proto3" json:"headers,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Body []byte `protobuf:"bytes,3,opt,name=body,proto3" json:"body,omitempty"` +} + +func (x *ResponseMessage) Reset() { + *x = ResponseMessage{} + if protoimpl.UnsafeEnabled { + mi := &file_message_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ResponseMessage) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ResponseMessage) ProtoMessage() {} + +func (x *ResponseMessage) ProtoReflect() protoreflect.Message { + mi := &file_message_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ResponseMessage.ProtoReflect.Descriptor instead. +func (*ResponseMessage) Descriptor() ([]byte, []int) { + return file_message_proto_rawDescGZIP(), []int{1} +} + +func (x *ResponseMessage) GetStatusCode() int32 { + if x != nil { + return x.StatusCode + } + return 0 +} + +func (x *ResponseMessage) GetHeaders() map[string]string { + if x != nil { + return x.Headers + } + return nil +} + +func (x *ResponseMessage) GetBody() []byte { + if x != nil { + return x.Body + } + return nil +} + +var File_message_proto protoreflect.FileDescriptor + +var file_message_proto_rawDesc = []byte{ + 0x0a, 0x0d, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x30, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x6b, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x2e, 0x76, + 0x33, 0x22, 0x99, 0x02, 0x0a, 0x0e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x4d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x02, 0x69, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x04, 0x68, 0x6f, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x16, 0x0a, 0x06, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x65, + 0x74, 0x68, 0x6f, 0x64, 0x12, 0x67, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, + 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x4d, 0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x65, 0x78, + 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, + 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x63, + 0x65, 0x69, 0x76, 0x65, 0x72, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, + 0x04, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, + 0x79, 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xec, 0x01, + 0x0a, 0x0f, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, + 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x63, 0x6f, 0x64, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x6f, + 0x64, 0x65, 0x12, 0x68, 0x0a, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x18, 0x02, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x4e, 0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x65, 0x78, 0x74, 0x65, + 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x68, + 0x74, 0x74, 0x70, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, + 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x07, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x62, 0x6f, 0x64, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x62, 0x6f, 0x64, 0x79, + 0x1a, 0x3a, 0x0a, 0x0c, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, + 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, + 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x48, 0x5a, 0x46, + 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, + 0x74, 0x66, 0x6c, 0x6f, 0x77, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2d, 0x65, 0x6e, 0x76, + 0x6f, 0x79, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, + 0x6c, 0x74, 0x65, 0x72, 0x73, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, + 0x76, 0x65, 0x72, 0x2f, 0x76, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_message_proto_rawDescOnce sync.Once + file_message_proto_rawDescData = file_message_proto_rawDesc +) + +func file_message_proto_rawDescGZIP() []byte { + file_message_proto_rawDescOnce.Do(func() { + file_message_proto_rawDescData = protoimpl.X.CompressGZIP(file_message_proto_rawDescData) + }) + return file_message_proto_rawDescData +} + +var file_message_proto_msgTypes = make([]protoimpl.MessageInfo, 4) +var file_message_proto_goTypes = []interface{}{ + (*RequestMessage)(nil), // 0: envoy.extensions.filters.http.kuscia_receiver.v3.RequestMessage + (*ResponseMessage)(nil), // 1: envoy.extensions.filters.http.kuscia_receiver.v3.ResponseMessage + nil, // 2: envoy.extensions.filters.http.kuscia_receiver.v3.RequestMessage.HeadersEntry + nil, // 3: envoy.extensions.filters.http.kuscia_receiver.v3.ResponseMessage.HeadersEntry +} +var file_message_proto_depIdxs = []int32{ + 2, // 0: envoy.extensions.filters.http.kuscia_receiver.v3.RequestMessage.headers:type_name -> envoy.extensions.filters.http.kuscia_receiver.v3.RequestMessage.HeadersEntry + 3, // 1: envoy.extensions.filters.http.kuscia_receiver.v3.ResponseMessage.headers:type_name -> envoy.extensions.filters.http.kuscia_receiver.v3.ResponseMessage.HeadersEntry + 2, // [2:2] is the sub-list for method output_type + 2, // [2:2] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_message_proto_init() } +func file_message_proto_init() { + if File_message_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_message_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*RequestMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_message_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ResponseMessage); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_message_proto_rawDesc, + NumEnums: 0, + NumMessages: 4, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_message_proto_goTypes, + DependencyIndexes: file_message_proto_depIdxs, + MessageInfos: file_message_proto_msgTypes, + }.Build() + File_message_proto = out.File + file_message_proto_rawDesc = nil + file_message_proto_goTypes = nil + file_message_proto_depIdxs = nil +} diff --git a/kuscia/api/filters/http/kuscia_receiver/v3/message.pb.validate.go b/kuscia/api/filters/http/kuscia_receiver/v3/message.pb.validate.go new file mode 100644 index 0000000..8cdc466 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_receiver/v3/message.pb.validate.go @@ -0,0 +1,254 @@ +// Code generated by protoc-gen-validate. DO NOT EDIT. +// source: message.proto + +package v3 + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/mail" + "net/url" + "regexp" + "sort" + "strings" + "time" + "unicode/utf8" + + "google.golang.org/protobuf/types/known/anypb" +) + +// ensure the imports are used +var ( + _ = bytes.MinRead + _ = errors.New("") + _ = fmt.Print + _ = utf8.UTFMax + _ = (*regexp.Regexp)(nil) + _ = (*strings.Reader)(nil) + _ = net.IPv4len + _ = time.Duration(0) + _ = (*url.URL)(nil) + _ = (*mail.Address)(nil) + _ = anypb.Any{} + _ = sort.Sort +) + +// Validate checks the field values on RequestMessage with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *RequestMessage) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on RequestMessage with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in RequestMessageMultiError, +// or nil if none found. +func (m *RequestMessage) ValidateAll() error { + return m.validate(true) +} + +func (m *RequestMessage) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Id + + // no validation rules for Host + + // no validation rules for Path + + // no validation rules for Method + + // no validation rules for Headers + + // no validation rules for Body + + if len(errors) > 0 { + return RequestMessageMultiError(errors) + } + + return nil +} + +// RequestMessageMultiError is an error wrapping multiple validation errors +// returned by RequestMessage.ValidateAll() if the designated constraints +// aren't met. +type RequestMessageMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m RequestMessageMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m RequestMessageMultiError) AllErrors() []error { return m } + +// RequestMessageValidationError is the validation error returned by +// RequestMessage.Validate if the designated constraints aren't met. +type RequestMessageValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e RequestMessageValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e RequestMessageValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e RequestMessageValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e RequestMessageValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e RequestMessageValidationError) ErrorName() string { return "RequestMessageValidationError" } + +// Error satisfies the builtin error interface +func (e RequestMessageValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sRequestMessage.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = RequestMessageValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = RequestMessageValidationError{} + +// Validate checks the field values on ResponseMessage with the rules defined +// in the proto definition for this message. If any rules are violated, the +// first error encountered is returned, or nil if there are no violations. +func (m *ResponseMessage) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ResponseMessage with the rules +// defined in the proto definition for this message. If any rules are +// violated, the result is a list of violation errors wrapped in +// ResponseMessageMultiError, or nil if none found. +func (m *ResponseMessage) ValidateAll() error { + return m.validate(true) +} + +func (m *ResponseMessage) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for StatusCode + + // no validation rules for Headers + + // no validation rules for Body + + if len(errors) > 0 { + return ResponseMessageMultiError(errors) + } + + return nil +} + +// ResponseMessageMultiError is an error wrapping multiple validation errors +// returned by ResponseMessage.ValidateAll() if the designated constraints +// aren't met. +type ResponseMessageMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ResponseMessageMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ResponseMessageMultiError) AllErrors() []error { return m } + +// ResponseMessageValidationError is the validation error returned by +// ResponseMessage.Validate if the designated constraints aren't met. +type ResponseMessageValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ResponseMessageValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ResponseMessageValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ResponseMessageValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ResponseMessageValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ResponseMessageValidationError) ErrorName() string { return "ResponseMessageValidationError" } + +// Error satisfies the builtin error interface +func (e ResponseMessageValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sResponseMessage.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ResponseMessageValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ResponseMessageValidationError{} diff --git a/kuscia/api/filters/http/kuscia_receiver/v3/message.proto b/kuscia/api/filters/http/kuscia_receiver/v3/message.proto new file mode 100644 index 0000000..8f5bdf5 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_receiver/v3/message.proto @@ -0,0 +1,36 @@ +// Copyright 2024 Ant Group Co., 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. +// + +syntax = "proto3"; + +package envoy.extensions.filters.http.kuscia_receiver.v3; +option go_package = "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/receiver/v3"; + +message RequestMessage { + string id = 1; + string host = 2; + string path = 3; + string method = 4; + map headers = 5; + bytes body = 6; +} +message ResponseMessage { + int32 status_code = 1; + map headers = 2; + bytes body = 3; + bool end_stream = 4; + bool chunk_data = 5; + int32 index = 6; +} \ No newline at end of file diff --git a/kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.go b/kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.go new file mode 100644 index 0000000..72ec62a --- /dev/null +++ b/kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.go @@ -0,0 +1,260 @@ +// Copyright 2024 Ant Group Co., 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. +// + +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.32.0 +// protoc v3.20.3 +// source: receiver.proto + +package v3 + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type ReceiverRule struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Source string `protobuf:"bytes,1,opt,name=source,proto3" json:"source,omitempty"` + Destination string `protobuf:"bytes,2,opt,name=destination,proto3" json:"destination,omitempty"` + Svc string `protobuf:"bytes,3,opt,name=svc,proto3" json:"svc,omitempty"` +} + +func (x *ReceiverRule) Reset() { + *x = ReceiverRule{} + if protoimpl.UnsafeEnabled { + mi := &file_receiver_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ReceiverRule) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ReceiverRule) ProtoMessage() {} + +func (x *ReceiverRule) ProtoReflect() protoreflect.Message { + mi := &file_receiver_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ReceiverRule.ProtoReflect.Descriptor instead. +func (*ReceiverRule) Descriptor() ([]byte, []int) { + return file_receiver_proto_rawDescGZIP(), []int{0} +} + +func (x *ReceiverRule) GetSource() string { + if x != nil { + return x.Source + } + return "" +} + +func (x *ReceiverRule) GetDestination() string { + if x != nil { + return x.Destination + } + return "" +} + +func (x *ReceiverRule) GetSvc() string { + if x != nil { + return x.Svc + } + return "" +} + +type Receiver struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + SelfNamespace string `protobuf:"bytes,1,opt,name=self_namespace,json=selfNamespace,proto3" json:"self_namespace,omitempty"` + Rules []*ReceiverRule `protobuf:"bytes,2,rep,name=rules,proto3" json:"rules,omitempty"` +} + +func (x *Receiver) Reset() { + *x = Receiver{} + if protoimpl.UnsafeEnabled { + mi := &file_receiver_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Receiver) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Receiver) ProtoMessage() {} + +func (x *Receiver) ProtoReflect() protoreflect.Message { + mi := &file_receiver_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Receiver.ProtoReflect.Descriptor instead. +func (*Receiver) Descriptor() ([]byte, []int) { + return file_receiver_proto_rawDescGZIP(), []int{1} +} + +func (x *Receiver) GetSelfNamespace() string { + if x != nil { + return x.SelfNamespace + } + return "" +} + +func (x *Receiver) GetRules() []*ReceiverRule { + if x != nil { + return x.Rules + } + return nil +} + +var File_receiver_proto protoreflect.FileDescriptor + +var file_receiver_proto_rawDesc = []byte{ + 0x0a, 0x0e, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x12, 0x30, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, + 0x6e, 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, 0x70, 0x2e, + 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x2e, + 0x76, 0x33, 0x22, 0x5a, 0x0a, 0x0c, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x75, + 0x6c, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, + 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0b, 0x64, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, + 0x73, 0x76, 0x63, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x73, 0x76, 0x63, 0x22, 0x87, + 0x01, 0x0a, 0x08, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x12, 0x25, 0x0a, 0x0e, 0x73, + 0x65, 0x6c, 0x66, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x73, 0x65, 0x6c, 0x66, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x12, 0x54, 0x0a, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x3e, 0x2e, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2e, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, + 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, 0x73, 0x2e, 0x68, 0x74, 0x74, + 0x70, 0x2e, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, + 0x72, 0x2e, 0x76, 0x33, 0x2e, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x52, 0x75, 0x6c, + 0x65, 0x52, 0x05, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x42, 0x48, 0x5a, 0x46, 0x67, 0x69, 0x74, 0x68, + 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x66, 0x6c, 0x6f, + 0x77, 0x2f, 0x6b, 0x75, 0x73, 0x63, 0x69, 0x61, 0x2d, 0x65, 0x6e, 0x76, 0x6f, 0x79, 0x2f, 0x6b, + 0x75, 0x73, 0x63, 0x69, 0x61, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x66, 0x69, 0x6c, 0x74, 0x65, 0x72, + 0x73, 0x2f, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x72, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x2f, + 0x76, 0x33, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_receiver_proto_rawDescOnce sync.Once + file_receiver_proto_rawDescData = file_receiver_proto_rawDesc +) + +func file_receiver_proto_rawDescGZIP() []byte { + file_receiver_proto_rawDescOnce.Do(func() { + file_receiver_proto_rawDescData = protoimpl.X.CompressGZIP(file_receiver_proto_rawDescData) + }) + return file_receiver_proto_rawDescData +} + +var file_receiver_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_receiver_proto_goTypes = []interface{}{ + (*ReceiverRule)(nil), // 0: envoy.extensions.filters.http.kuscia_receiver.v3.ReceiverRule + (*Receiver)(nil), // 1: envoy.extensions.filters.http.kuscia_receiver.v3.Receiver +} +var file_receiver_proto_depIdxs = []int32{ + 0, // 0: envoy.extensions.filters.http.kuscia_receiver.v3.Receiver.rules:type_name -> envoy.extensions.filters.http.kuscia_receiver.v3.ReceiverRule + 1, // [1:1] is the sub-list for method output_type + 1, // [1:1] is the sub-list for method input_type + 1, // [1:1] is the sub-list for extension type_name + 1, // [1:1] is the sub-list for extension extendee + 0, // [0:1] is the sub-list for field type_name +} + +func init() { file_receiver_proto_init() } +func file_receiver_proto_init() { + if File_receiver_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_receiver_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ReceiverRule); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_receiver_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Receiver); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_receiver_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 0, + }, + GoTypes: file_receiver_proto_goTypes, + DependencyIndexes: file_receiver_proto_depIdxs, + MessageInfos: file_receiver_proto_msgTypes, + }.Build() + File_receiver_proto = out.File + file_receiver_proto_rawDesc = nil + file_receiver_proto_goTypes = nil + file_receiver_proto_depIdxs = nil +} diff --git a/kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.validate.go b/kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.validate.go new file mode 100644 index 0000000..9bc6848 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.validate.go @@ -0,0 +1,276 @@ +// Code generated by protoc-gen-validate. DO NOT EDIT. +// source: receiver.proto + +package v3 + +import ( + "bytes" + "errors" + "fmt" + "net" + "net/mail" + "net/url" + "regexp" + "sort" + "strings" + "time" + "unicode/utf8" + + "google.golang.org/protobuf/types/known/anypb" +) + +// ensure the imports are used +var ( + _ = bytes.MinRead + _ = errors.New("") + _ = fmt.Print + _ = utf8.UTFMax + _ = (*regexp.Regexp)(nil) + _ = (*strings.Reader)(nil) + _ = net.IPv4len + _ = time.Duration(0) + _ = (*url.URL)(nil) + _ = (*mail.Address)(nil) + _ = anypb.Any{} + _ = sort.Sort +) + +// Validate checks the field values on ReceiverRule with the rules defined in +// the proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *ReceiverRule) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on ReceiverRule with the rules defined +// in the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in ReceiverRuleMultiError, or +// nil if none found. +func (m *ReceiverRule) ValidateAll() error { + return m.validate(true) +} + +func (m *ReceiverRule) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for Source + + // no validation rules for Destination + + // no validation rules for Svc + + if len(errors) > 0 { + return ReceiverRuleMultiError(errors) + } + + return nil +} + +// ReceiverRuleMultiError is an error wrapping multiple validation errors +// returned by ReceiverRule.ValidateAll() if the designated constraints aren't met. +type ReceiverRuleMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ReceiverRuleMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ReceiverRuleMultiError) AllErrors() []error { return m } + +// ReceiverRuleValidationError is the validation error returned by +// ReceiverRule.Validate if the designated constraints aren't met. +type ReceiverRuleValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ReceiverRuleValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ReceiverRuleValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ReceiverRuleValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ReceiverRuleValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ReceiverRuleValidationError) ErrorName() string { return "ReceiverRuleValidationError" } + +// Error satisfies the builtin error interface +func (e ReceiverRuleValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sReceiverRule.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ReceiverRuleValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ReceiverRuleValidationError{} + +// Validate checks the field values on Receiver with the rules defined in the +// proto definition for this message. If any rules are violated, the first +// error encountered is returned, or nil if there are no violations. +func (m *Receiver) Validate() error { + return m.validate(false) +} + +// ValidateAll checks the field values on Receiver with the rules defined in +// the proto definition for this message. If any rules are violated, the +// result is a list of violation errors wrapped in ReceiverMultiError, or nil +// if none found. +func (m *Receiver) ValidateAll() error { + return m.validate(true) +} + +func (m *Receiver) validate(all bool) error { + if m == nil { + return nil + } + + var errors []error + + // no validation rules for SelfNamespace + + for idx, item := range m.GetRules() { + _, _ = idx, item + + if all { + switch v := interface{}(item).(type) { + case interface{ ValidateAll() error }: + if err := v.ValidateAll(); err != nil { + errors = append(errors, ReceiverValidationError{ + field: fmt.Sprintf("Rules[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + case interface{ Validate() error }: + if err := v.Validate(); err != nil { + errors = append(errors, ReceiverValidationError{ + field: fmt.Sprintf("Rules[%v]", idx), + reason: "embedded message failed validation", + cause: err, + }) + } + } + } else if v, ok := interface{}(item).(interface{ Validate() error }); ok { + if err := v.Validate(); err != nil { + return ReceiverValidationError{ + field: fmt.Sprintf("Rules[%v]", idx), + reason: "embedded message failed validation", + cause: err, + } + } + } + + } + + if len(errors) > 0 { + return ReceiverMultiError(errors) + } + + return nil +} + +// ReceiverMultiError is an error wrapping multiple validation errors returned +// by Receiver.ValidateAll() if the designated constraints aren't met. +type ReceiverMultiError []error + +// Error returns a concatenation of all the error messages it wraps. +func (m ReceiverMultiError) Error() string { + var msgs []string + for _, err := range m { + msgs = append(msgs, err.Error()) + } + return strings.Join(msgs, "; ") +} + +// AllErrors returns a list of validation violation errors. +func (m ReceiverMultiError) AllErrors() []error { return m } + +// ReceiverValidationError is the validation error returned by +// Receiver.Validate if the designated constraints aren't met. +type ReceiverValidationError struct { + field string + reason string + cause error + key bool +} + +// Field function returns field value. +func (e ReceiverValidationError) Field() string { return e.field } + +// Reason function returns reason value. +func (e ReceiverValidationError) Reason() string { return e.reason } + +// Cause function returns cause value. +func (e ReceiverValidationError) Cause() error { return e.cause } + +// Key function returns key value. +func (e ReceiverValidationError) Key() bool { return e.key } + +// ErrorName returns error name. +func (e ReceiverValidationError) ErrorName() string { return "ReceiverValidationError" } + +// Error satisfies the builtin error interface +func (e ReceiverValidationError) Error() string { + cause := "" + if e.cause != nil { + cause = fmt.Sprintf(" | caused by: %v", e.cause) + } + + key := "" + if e.key { + key = "key for " + } + + return fmt.Sprintf( + "invalid %sReceiver.%s: %s%s", + key, + e.field, + e.reason, + cause) +} + +var _ error = ReceiverValidationError{} + +var _ interface { + Field() string + Reason() string + Key() bool + Cause() error + ErrorName() string +} = ReceiverValidationError{} diff --git a/kuscia/api/filters/http/kuscia_receiver/v3/receiver.proto b/kuscia/api/filters/http/kuscia_receiver/v3/receiver.proto new file mode 100644 index 0000000..5d85098 --- /dev/null +++ b/kuscia/api/filters/http/kuscia_receiver/v3/receiver.proto @@ -0,0 +1,30 @@ +// Copyright 2024 Ant Group Co., 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. +// + +syntax = "proto3"; + +package envoy.extensions.filters.http.kuscia_receiver.v3; +option go_package = "github.com/secretflow/kuscia-envoy/kuscia/api/filters/http/receiver/v3"; + +message ReceiverRule { + string source = 1; + string destination = 2; + string service = 3; +} + +message Receiver { + string self_namespace = 1; + repeated ReceiverRule rules = 2; +} diff --git a/kuscia/source/filters/http/kuscia_common/BUILD b/kuscia/source/filters/http/kuscia_common/BUILD index 31a65f2..9606584 100755 --- a/kuscia/source/filters/http/kuscia_common/BUILD +++ b/kuscia/source/filters/http/kuscia_common/BUILD @@ -11,13 +11,21 @@ api_proto_package() #"@envoy//envoy/http:header_map_interface", envoy_cc_library( name = "kuscia_common", - srcs = ["kuscia_header.cc"], + srcs = [ + "kuscia_header.cc", + "framer.cc", + "coder.cc", + ], hdrs = [ "common.h", "kuscia_header.h", + "framer.h", + "coder.h", ], repository = "@envoy", deps = [ "@envoy//envoy/http:header_map_interface", + "@com_googlesource_code_re2//:re2", + "@envoy//source/common/buffer:buffer_lib", ], ) diff --git a/kuscia/source/filters/http/kuscia_common/coder.cc b/kuscia/source/filters/http/kuscia_common/coder.cc new file mode 100644 index 0000000..02cf8aa --- /dev/null +++ b/kuscia/source/filters/http/kuscia_common/coder.cc @@ -0,0 +1,41 @@ +// Copyright 2024 Ant Group Co., 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 "kuscia/source/filters/http/kuscia_common/coder.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaCommon { + +DecodeStatus KusciaCommon::Decoder::decode(Envoy::Buffer::Instance& data, google::protobuf::Message &message) +{ + DecodeStatus status = frameReader_.read(data); + if (status != DecodeStatus::Ok) { + return status; + } + + auto data_frame = frameReader_.getDataFrame(); + + if (!message.ParseFromArray(data_frame.data(), data_frame.size())) { + return DecodeStatus::ErrorInvalidData; + } + + return DecodeStatus::Ok; +} + +} // namespace KusciaCommon +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_common/coder.h b/kuscia/source/filters/http/kuscia_common/coder.h new file mode 100644 index 0000000..983380b --- /dev/null +++ b/kuscia/source/filters/http/kuscia_common/coder.h @@ -0,0 +1,36 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "kuscia/source/filters/http/kuscia_common/framer.h" +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaCommon { + +class Decoder { +public: + DecodeStatus decode(Envoy::Buffer::Instance& data, google::protobuf::Message& message); + +private: + LengthDelimitedFrameReader frameReader_; +}; + +} // namespace KusciaCommon +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_common/common.h b/kuscia/source/filters/http/kuscia_common/common.h index 08d68d4..9d7f1c9 100755 --- a/kuscia/source/filters/http/kuscia_common/common.h +++ b/kuscia/source/filters/http/kuscia_common/common.h @@ -1,18 +1,17 @@ // Copyright 2023 Ant Group Co., 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. - #pragma once namespace Envoy { @@ -20,19 +19,19 @@ namespace Extensions { namespace HttpFilters { namespace KusciaCommon { -#define KUSCIA_RETURN_IF(expr)\ - do {\ - if (expr) {\ - return;\ - }\ - } while (false) +#define KUSCIA_RETURN_IF(expr) \ + do { \ + if (expr) { \ + return; \ + } \ + } while (false) -#define KUSCIA_RETURN_RET_IF(expr, ret)\ - do {\ - if (expr) {\ - return ret;\ - }\ - } while (false) +#define KUSCIA_RETURN_RET_IF(expr, ret) \ + do { \ + if (expr) { \ + return ret; \ + } \ + } while (false) } // namespace KusciaCommon } // namespace HttpFilters diff --git a/kuscia/source/filters/http/kuscia_common/framer.cc b/kuscia/source/filters/http/kuscia_common/framer.cc new file mode 100644 index 0000000..f79d7ba --- /dev/null +++ b/kuscia/source/filters/http/kuscia_common/framer.cc @@ -0,0 +1,102 @@ +// Copyright 2024 Ant Group Co., 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 + +#include "kuscia/source/filters/http/kuscia_common/framer.h" +#include "framer.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaCommon { + + +static const std::map decodeStatusMessageMap = { + {DecodeStatus::NeedMoreData, "need more data"}, + {DecodeStatus::ErrorObjectTooLarge, "object too large"}, + {DecodeStatus::ErrorInvalidData, "invalid data"} +}; + +absl::string_view decodeStatusString(DecodeStatus status) { + auto it = decodeStatusMessageMap.find(status); + if (it != decodeStatusMessageMap.end()) { + return it->second; + } else { + return ""; + } +} + +DecodeStatus LengthDelimitedFrameReader::read(Buffer::Instance& input) +{ + if (remaining_ == 0) { + uint32_t frameLength; + if (!readByLen(input, sizeof(frameLength), len_frame_)) { + return DecodeStatus::NeedMoreData; + } + + std::memcpy(&frameLength, len_frame_.data(), sizeof(frameLength)); + remaining_ = ntohl(frameLength); + + if (remaining_ > maxBytes_) { + return DecodeStatus::ErrorObjectTooLarge; + } + + len_frame_.resize(0); + data_frame_.resize(0); + } + + if (!readByLen(input, remaining_, data_frame_)) { + return DecodeStatus::NeedMoreData; + } + + remaining_ = 0; + return DecodeStatus::Ok; +} + +bool LengthDelimitedFrameReader::readByLen(Buffer::Instance& input, size_t len, std::vector& frame) +{ + size_t frame_size = frame.size(); + size_t input_len = input.length(); + + if (frame_size + input_len < len) { + ENVOY_LOG(info, "Need more input data, frame size: {} + input-len: {} < {}", frame_size, input_len, len); + + frame.resize(frame_size + input_len); + input.copyOut(0, input_len, frame.data() + frame_size); + input.drain(input_len); + return false; // need more input data + } + + frame.resize(len); + input.copyOut(0, len - frame_size, frame.data() + frame_size); + input.drain(len - frame_size); + + return true; +} + +void KusciaCommon::LengthDelimitedFrameWriter::write(const char data[], uint32_t size, Buffer::OwnedImpl &output) +{ + uint32_t net_size = htonl(size); + output.add(&net_size, sizeof(net_size)); + output.add(data, size); +} + +} // namespace KusciaCommon +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_common/framer.h b/kuscia/source/filters/http/kuscia_common/framer.h new file mode 100644 index 0000000..c0df2ad --- /dev/null +++ b/kuscia/source/filters/http/kuscia_common/framer.h @@ -0,0 +1,60 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/logger.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaCommon { + +enum class DecodeStatus { + Ok, + NeedMoreData, + ErrorObjectTooLarge, + ErrorInvalidData, +}; + +extern absl::string_view decodeStatusString(DecodeStatus status); + +class LengthDelimitedFrameReader : public Logger::Loggable { +public: + LengthDelimitedFrameReader() : remaining_(0), maxBytes_(16 * 1024 * 1024) {} + + DecodeStatus read(Buffer::Instance& input); + + const std::vector& getDataFrame() const { return data_frame_; }; + +private: + bool readByLen(Buffer::Instance& input, size_t len, std::vector& frame); + + size_t remaining_; + const size_t maxBytes_; + std::vector len_frame_; + std::vector data_frame_; +}; + +class LengthDelimitedFrameWriter { +public: + void write(const char data[], uint32_t size, Buffer::OwnedImpl& output); +}; + +} // namespace KusciaCommon +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_common/kuscia_header.h b/kuscia/source/filters/http/kuscia_common/kuscia_header.h index 9187671..e672367 100755 --- a/kuscia/source/filters/http/kuscia_common/kuscia_header.h +++ b/kuscia/source/filters/http/kuscia_common/kuscia_header.h @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. - #pragma once #include "envoy/http/header_map.h" +#include "re2/re2.h" + namespace Envoy { namespace Extensions { namespace HttpFilters { @@ -32,20 +33,36 @@ const Http::LowerCaseString HeaderKeyKusciaToken("Kuscia-Token"); const Http::LowerCaseString HeaderKeyKusciaHost("Kuscia-Host"); const Http::LowerCaseString HeaderKeyOriginSource("Kuscia-Origin-Source"); - const Http::LowerCaseString HeaderKeyErrorMessage("Kuscia-Error-Message"); -const Http::LowerCaseString HeaderKeyFmtError("Kuscia-Error-Formatted"); const Http::LowerCaseString HeaderKeyErrorMessageInternal("Kuscia-Error-Message-Internal"); const Http::LowerCaseString HeaderKeyRecordBody("Kuscia-Record-Body"); const Http::LowerCaseString HeaderKeyEncryptVersion("Kuscia-Encrypt-Version"); const Http::LowerCaseString HeaderKeyEncryptIv("Kuscia-Encrypt-Iv"); +const Http::LowerCaseString HeaderKeyForwardRequestId("Kuscia-Foward-Request-Id"); + class KusciaHeader { - public: - static absl::optional getSource(const Http::RequestHeaderMap& headers); +public: + static absl::optional getSource(const Http::RequestHeaderMap& headers); }; +// receiver.${peer}.svc/poll?timeout=xxx&service=xxx +const re2::RE2 PollHostPattern("receiver\\.(.*)\\.svc"); + +const std::string PollPathPrefix("/poll?"); +const std::string ReplyPathPrefix("/reply?"); +const std::string ServiceParamKey("service"); +const std::string TimeoutParamKey("timeout"); +const std::string RequestIdParamKey("msgid"); + +const std::string GatewayClusterName("handshake-cluster"); +const std::string GatewayHostName("kuscia-handshake"); +const std::string GatewayRegisterPath("/svc/register"); +const std::string GatewayUnregisterPath("/svc/unregister"); + +const std::string InternalClusterHost("127.0.0.1:80"); + } // namespace KusciaCommon } // namespace HttpFilters } // namespace Extensions diff --git a/kuscia/source/filters/http/kuscia_gress/BUILD b/kuscia/source/filters/http/kuscia_gress/BUILD index fbca5bc..0cc8360 100755 --- a/kuscia/source/filters/http/kuscia_gress/BUILD +++ b/kuscia/source/filters/http/kuscia_gress/BUILD @@ -16,6 +16,9 @@ envoy_cc_library( "@envoy//source/common/buffer:buffer_lib", "@envoy//source/common/http:header_utility_lib", "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy//source/common/http:codes_lib", + "@com_github_nlohmann_json//:json", + "@envoy//source/common/api:os_sys_calls_lib" ], ) diff --git a/kuscia/source/filters/http/kuscia_gress/gress_filter.cc b/kuscia/source/filters/http/kuscia_gress/gress_filter.cc index cad96db..8dae93e 100755 --- a/kuscia/source/filters/http/kuscia_gress/gress_filter.cc +++ b/kuscia/source/filters/http/kuscia_gress/gress_filter.cc @@ -12,239 +12,277 @@ // See the License for the specific language governing permissions and // limitations under the License. - #include "kuscia/source/filters/http/kuscia_gress/gress_filter.h" - #include "fmt/format.h" +#include "kuscia/source/filters/http/kuscia_common/kuscia_header.h" +#include "source/common/http/codes.h" #include "source/common/http/header_utility.h" #include "source/common/http/headers.h" - -#include "kuscia/source/filters/http/kuscia_common/kuscia_header.h" +#include +#include +#include +#include namespace Envoy { namespace Extensions { namespace HttpFilters { namespace KusciaGress { -static void adjustContentLength(Http::RequestOrResponseHeaderMap& headers, uint64_t delta_length) { - auto length_header = headers.getContentLengthValue(); - if (!length_header.empty()) { - uint64_t old_length; - if (absl::SimpleAtoi(length_header, &old_length)) { - if (old_length != 0) { - headers.setContentLength(old_length + delta_length); - } - } +static std::string replaceNamespaceInHost(absl::string_view host, + absl::string_view new_namespace) { + std::vector fields = absl::StrSplit(host, "."); + for (std::size_t i = 2; i < fields.size(); i++) { + if (fields[i] == "svc") { + fields[i - 1] = new_namespace; + return absl::StrJoin(fields, "."); } + } + return ""; } -static std::string replaceNamespaceInHost(absl::string_view host, absl::string_view new_namespace) { - std::vector fields = absl::StrSplit(host, "."); - for (std::size_t i = 2; i < fields.size(); i++) { - if (fields[i] == "svc") { - fields[i - 1] = new_namespace; - return absl::StrJoin(fields, "."); - } - } - return ""; +static std::string getGatewayDesc(const std::string& domain, const std::string& instance, + const std::string& listener) { + return fmt::format("{}/{}/{}", domain, instance, listener); } -RewriteHostConfig::RewriteHostConfig(const RewriteHost& config) : - rewrite_policy_(config.rewrite_policy()), - header_(config.header()), - specified_host_(config.specified_host()) { - path_matchers_.reserve(config.path_matchers_size()); - for (const auto& pm : config.path_matchers()) { - PathMatcherConstSharedPtr matcher(new Envoy::Matchers::PathMatcher(pm)); - path_matchers_.emplace_back(matcher); - } +static std::string getListener(const StreamInfo::StreamInfo& stream_info) { + std::string address; + auto& provider = stream_info.downstreamAddressProvider(); + if (provider.localAddress() != nullptr) { + address = provider.localAddress()->asString(); + } + if (address.empty()) { + return "-"; + } + return absl::EndsWith(address, ":80") ? "internal" : "external"; } -GressFilterConfig::GressFilterConfig(const GressPbConfig& config) : - instance_(config.instance()), - self_namespace_(config.self_namespace()), - add_origin_source_(config.add_origin_source()), - max_logging_body_size_per_reqeuest_(config.max_logging_body_size_per_reqeuest()) { - rewrite_host_config_.reserve(config.rewrite_host_config_size()); - for (const auto& rh : config.rewrite_host_config()) { - rewrite_host_config_.emplace_back(RewriteHostConfig(rh)); - } +static std::string getCause(const StreamInfo::StreamInfo& stream_info) { + std::string cause; + if (stream_info.responseCodeDetails().has_value()) { + cause = stream_info.responseCodeDetails().value(); + } + return cause; } -Http::FilterHeadersStatus GressFilter::decodeHeaders(Http::RequestHeaderMap& headers, - bool) { - // store some useful headers - request_id_ = std::string(headers.getRequestIdValue()); - host_ = std::string(headers.getHostValue()); - auto record = headers.getByKey(KusciaCommon::HeaderKeyRecordBody); - if (record.has_value() && record.value() == "true") { - record_request_body_ = true; - record_response_body_ = true; - } +std::string strip(absl::string_view sv) { return std::string(sv.data(), sv.size()); } - // rewrite host to choose a new route - if (rewriteHost(headers)) { - decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); - } else { - // replace ".svc:" with ".svc" for internal request - size_t n = host_.rfind(".svc:"); - if (n != std::string::npos) { - std::string substr = host_.substr(0, n + 4); - headers.setHost(substr); - decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); - } +std::string getHeaderValue(const Http::ResponseHeaderMap& headers, + const Http::LowerCaseString& key) { + auto value_header = headers.get(key); + if (!value_header.empty() && value_header[0] != nullptr && !value_header[0]->value().empty()) { + return strip(value_header[0]->value().getStringView()); + } + return ""; +} + +RewriteHostConfig::RewriteHostConfig(const RewriteHost& config) + : rewrite_policy_(config.rewrite_policy()), header_(config.header()), + specified_host_(config.specified_host()) { + path_matchers_.reserve(config.path_matchers_size()); + for (const auto& pm : config.path_matchers()) { + PathMatcherConstSharedPtr matcher(new Envoy::Matchers::PathMatcher(pm)); + path_matchers_.emplace_back(matcher); + } +} + +GressFilterConfig::GressFilterConfig(const GressPbConfig& config) + : instance_(config.instance()), self_namespace_(config.self_namespace()), + add_origin_source_(config.add_origin_source()), + max_logging_body_size_per_reqeuest_(config.max_logging_body_size_per_reqeuest()) { + rewrite_host_config_.reserve(config.rewrite_host_config_size()); + for (const auto& rh : config.rewrite_host_config()) { + rewrite_host_config_.emplace_back(RewriteHostConfig(rh)); + } +} + +Http::FilterHeadersStatus GressFilter::decodeHeaders(Http::RequestHeaderMap& headers, bool) { + // store some useful headers + request_id_ = std::string(headers.getRequestIdValue()); + host_ = std::string(headers.getHostValue()); + auto record = headers.getByKey(KusciaCommon::HeaderKeyRecordBody); + if (record.has_value() && record.value() == "true") { + record_request_body_ = true; + record_response_body_ = true; + } + + // rewrite host to choose a new route + if (rewriteHost(headers)) { + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); + } else { + // replace ".svc:" with ".svc" for internal request + size_t n = host_.rfind(".svc:"); + if (n != std::string::npos) { + std::string substr = host_.substr(0, n + 4); + headers.setHost(substr); + decoder_callbacks_->downstreamCallbacks()->clearRouteCache(); } + } - // add origin-source if not exist - if (config_->addOriginSource()) { - auto origin_source = headers.getByKey(KusciaCommon::HeaderKeyOriginSource) - .value_or(std::string()); - if (origin_source.empty()) { - headers.addCopy(KusciaCommon::HeaderKeyOriginSource, config_->selfNamespace()); - } + // add origin-source if not exist + if (config_->addOriginSource()) { + auto origin_source = + headers.getByKey(KusciaCommon::HeaderKeyOriginSource).value_or(std::string()); + if (origin_source.empty()) { + headers.addCopy(KusciaCommon::HeaderKeyOriginSource, config_->selfNamespace()); } + } - return Http::FilterHeadersStatus::Continue; + return Http::FilterHeadersStatus::Continue; } Http::FilterDataStatus GressFilter::decodeData(Buffer::Instance& data, bool end_stream) { - if (record_request_body_) { - record_request_body_ = recordBody(req_body_, data, end_stream, true); + if (record_request_body_) { + record_request_body_ = recordBody(req_body_, data, end_stream, true); + } + return Http::FilterDataStatus::Continue; +} + +Http::FilterHeadersStatus GressFilter::encodeHeaders(Http::ResponseHeaderMap& headers, bool) { + uint64_t status_code = 0; + if (absl::SimpleAtoi(headers.getStatusValue(), &status_code)) { + if (!(status_code >= 400 && status_code < 600)) { + return Http::FilterHeadersStatus::Continue; } - return Http::FilterDataStatus::Continue; -} - -Http::FilterHeadersStatus GressFilter::encodeHeaders(Http::ResponseHeaderMap& headers, - bool end_stream) { - // generate error msg - auto result = headers.get(KusciaCommon::HeaderKeyErrorMessage); - if (headers.getStatusValue() != "200") { - std::string err_msg; - auto result = headers.get(KusciaCommon::HeaderKeyErrorMessage); - if (result.empty()) { - auto inner_msg = headers.get(KusciaCommon::HeaderKeyErrorMessageInternal); - if (inner_msg.size() == 1 && inner_msg[0] != nullptr && !inner_msg[0]->value().empty()) { - err_msg = fmt::format("Domain {}.{}: {}", - config_->selfNamespace(), - config_->instance(), - inner_msg[0]->value().getStringView()); - headers.remove(KusciaCommon::HeaderKeyErrorMessageInternal); - } else { - err_msg = fmt::format("Domain {}.{}<--{} return http code {}.", - config_->selfNamespace(), - config_->instance(), - host_, - headers.getStatusValue()); - } - } else if (result[0] != nullptr) { - err_msg = fmt::format("Domain {}.{}<--{}", - config_->selfNamespace(), - config_->instance(), - result[0]->value().getStringView()); - - } - - headers.setCopy(KusciaCommon::HeaderKeyErrorMessage, err_msg); - if (end_stream) { - Envoy::Buffer::OwnedImpl body(err_msg); - adjustContentLength(headers, body.length()); - encoder_callbacks_->addEncodedData(body, true); - headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Text); - } + } + // 1. if error message key is set in response, then use it as error message + // 2. if internal error message key is set in response, then use it as error message + // 3. if neither of above, then use default error message + std::string error_message = getHeaderValue(headers, KusciaCommon::HeaderKeyErrorMessage); + bool formatted = false; + if (!error_message.empty()) { + formatted = true; + } else { + error_message = getHeaderValue(headers, KusciaCommon::HeaderKeyErrorMessageInternal); + if (error_message.empty()) { + error_message = Http::CodeUtility::toString(static_cast(status_code)); + } else { + headers.remove(KusciaCommon::HeaderKeyErrorMessageInternal); } - return Http::FilterHeadersStatus::Continue; + } + auto& stream_info = encoder_callbacks_->streamInfo(); + std::string rich_message = getRichMessage(stream_info, error_message, formatted); + headers.setCopy(KusciaCommon::HeaderKeyErrorMessage, rich_message); + return Http::FilterHeadersStatus::Continue; } Http::FilterDataStatus GressFilter::encodeData(Buffer::Instance& data, bool end_stream) { - if (record_response_body_) { - record_response_body_ = recordBody(resp_body_, data, end_stream, false); - } - return Http::FilterDataStatus::Continue; + if (record_response_body_) { + record_response_body_ = recordBody(resp_body_, data, end_stream, false); + } + return Http::FilterDataStatus::Continue; +} + +// The presence of trailers means the stream is ended, but encodeData() +// is never called with end_stream=true. +Http::FilterTrailersStatus GressFilter::encodeTrailers(Http::ResponseTrailerMap&) { + if (record_response_body_) { + Buffer::OwnedImpl data; + record_response_body_ = recordBody(resp_body_, data, true, false); + } + return Http::FilterTrailersStatus::Continue; +} + +std::string GressFilter::getRichMessage(const StreamInfo::StreamInfo& stream_info, + const std::string& error_message, bool formatted) { + std::string listener = getListener(stream_info); + std::string gateway_desc = + getGatewayDesc(config_->selfNamespace(), config_->instance(), listener); + std::string cause = getCause(stream_info); + std::string rich_message; + if (formatted) { + rich_message = fmt::format("<{}> => {}", gateway_desc, error_message); + } else if (cause == "via_upstream") { + rich_message = fmt::format("<{}> => ", gateway_desc, error_message); + } else { + rich_message = fmt::format("<{} ${}$ {}>", gateway_desc, cause, error_message); + } + return rich_message; } bool GressFilter::rewriteHost(Http::RequestHeaderMap& headers) { - for (const auto& rh : config_->rewriteHostConfig()) { - if (rewriteHost(headers, rh)) { - return true; - } + for (const auto& rh : config_->rewriteHostConfig()) { + if (rewriteHost(headers, rh)) { + return true; } - return false; + } + return false; } bool GressFilter::rewriteHost(Http::RequestHeaderMap& headers, const RewriteHostConfig& rh) { - auto header_value = headers.getByKey(Http::LowerCaseString(rh.header())).value_or(""); - if (header_value.empty()) { - return false; - } - - if (rh.pathMatchers().size() > 0) { - const absl::string_view path = headers.getPathValue(); - bool path_match = false; - for (const auto& pm : rh.pathMatchers()) { - if (pm->match(path)) { - path_match = true; - break; - } - } - if (!path_match) { - return false; - } - } + auto header_value = headers.getByKey(Http::LowerCaseString(rh.header())).value_or(""); + if (header_value.empty()) { + return false; + } - switch (rh.rewritePolicy()) { - case RewriteHost::RewriteHostWithHeader: { - headers.setHost(header_value); - return true; - } - case RewriteHost::RewriteNamespaceWithHeader: { - auto host_value = replaceNamespaceInHost(headers.getHostValue(), header_value); - if (!host_value.empty()) { - headers.setHost(host_value); - return true; - } + if (rh.pathMatchers().size() > 0) { + const absl::string_view path = headers.getPathValue(); + bool path_match = false; + for (const auto& pm : rh.pathMatchers()) { + if (pm->match(path)) { + path_match = true; break; + } } - case RewriteHost::RewriteHostWithSpecifiedHost: { - if (!rh.specifiedHost().empty()) { - headers.setHost(rh.specifiedHost()); - return true; - } - break; + if (!path_match) { + return false; } - default: - break; + } + + switch (rh.rewritePolicy()) { + case RewriteHost::RewriteHostWithHeader: { + headers.setHost(header_value); + return true; + } + case RewriteHost::RewriteNamespaceWithHeader: { + auto host_value = replaceNamespaceInHost(headers.getHostValue(), header_value); + if (!host_value.empty()) { + headers.setHost(host_value); + return true; + } + break; + } + case RewriteHost::RewriteHostWithSpecifiedHost: { + if (!rh.specifiedHost().empty()) { + headers.setHost(rh.specifiedHost()); + return true; } + break; + } + default: + break; + } - return false; + return false; } -bool GressFilter::recordBody(Buffer::OwnedImpl& body, Buffer::Instance& data, - bool end_stream, bool is_req) { - auto& stream_info = is_req ? decoder_callbacks_->streamInfo() : encoder_callbacks_->streamInfo(); - std::string body_key = is_req ? "request_body" : "response_body"; - - uint64_t logging_size = static_cast(config_->maxLoggingBodySizePerReqeuest()); - bool record_body = true; - if (data.length() > 0) { - if (logging_size > 0 && body.length() + data.length() > logging_size) { - ENVOY_LOG(info, "{} of {} already larger than {}, stop logging", - body_key, request_id_, logging_size); - record_body = false; - Buffer::OwnedImpl empty_buffer{}; - empty_buffer.move(body); - } else { - body.add(data); - } - } +bool GressFilter::recordBody(Buffer::OwnedImpl& body, Buffer::Instance& data, bool end_stream, + bool is_req) { + auto& stream_info = is_req ? decoder_callbacks_->streamInfo() : encoder_callbacks_->streamInfo(); + std::string body_key = is_req ? "request_body" : "response_body"; - if (end_stream && body.length() > 0) { - ProtobufWkt::Value value; - value.set_string_value(body.toString()); - ProtobufWkt::Struct metadata; - (*metadata.mutable_fields())[body_key] = value; - stream_info.setDynamicMetadata("envoy.kuscia", metadata); + uint64_t logging_size = static_cast(config_->maxLoggingBodySizePerReqeuest()); + bool record_body = true; + if (data.length() > 0) { + if (logging_size > 0 && body.length() + data.length() > logging_size) { + ENVOY_LOG(info, "{} of {} already larger than {}, stop logging", body_key, request_id_, + logging_size); + record_body = false; + Buffer::OwnedImpl empty_buffer{}; + empty_buffer.move(body); + } else { + body.add(data); } - return record_body; + } + + if (end_stream && body.length() > 0) { + ProtobufWkt::Value value; + value.set_string_value(body.toString()); + ProtobufWkt::Struct metadata; + (*metadata.mutable_fields())[body_key] = value; + stream_info.setDynamicMetadata("envoy.kuscia", metadata); + } + return record_body; } } // namespace KusciaGress diff --git a/kuscia/source/filters/http/kuscia_gress/gress_filter.h b/kuscia/source/filters/http/kuscia_gress/gress_filter.h index f23c7fc..c6e2355 100755 --- a/kuscia/source/filters/http/kuscia_gress/gress_filter.h +++ b/kuscia/source/filters/http/kuscia_gress/gress_filter.h @@ -12,20 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. - #pragma once -#include -#include - +#include "envoy/common/matchers.h" +#include "include/nlohmann/json.hpp" +#include "kuscia/api/filters/http/kuscia_gress/v3/gress.pb.h" #include "source/common/buffer/buffer_impl.h" #include "source/common/common/logger.h" -#include "source/extensions/filters/http/common/pass_through_filter.h" - -#include "kuscia/api/filters/http/kuscia_gress/v3/gress.pb.h" - -#include "envoy/common/matchers.h" #include "source/common/common/matchers.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" +#include +#include +#include namespace Envoy { namespace Extensions { @@ -38,96 +36,79 @@ using RewritePolicy = RewriteHost::RewritePolicy; using PathMatcherConstSharedPtr = std::shared_ptr; class RewriteHostConfig { - public: - explicit RewriteHostConfig(const RewriteHost& config); - - const std::string& header() const { - return header_; - } - RewritePolicy rewritePolicy() const { - return rewrite_policy_; - } - const std::string& specifiedHost() const { - return specified_host_; - } - - const std::vector& pathMatchers() const { - return path_matchers_; - } - - private: - RewriteHost::RewritePolicy rewrite_policy_; - std::string header_; - std::string specified_host_; - std::vector path_matchers_; +public: + explicit RewriteHostConfig(const RewriteHost& config); + + const std::string& header() const { return header_; } + RewritePolicy rewritePolicy() const { return rewrite_policy_; } + const std::string& specifiedHost() const { return specified_host_; } + + const std::vector& pathMatchers() const { return path_matchers_; } + +private: + RewriteHost::RewritePolicy rewrite_policy_; + std::string header_; + std::string specified_host_; + std::vector path_matchers_; }; class GressFilterConfig { - public: - explicit GressFilterConfig(const GressPbConfig& config); - const std::string& instance() const { - return instance_; - } - - const std::string& selfNamespace() const { - return self_namespace_; - } - - bool addOriginSource() const { - return add_origin_source_; - } - - int32_t maxLoggingBodySizePerReqeuest() { - return max_logging_body_size_per_reqeuest_; - } - - const std::vector& rewriteHostConfig() const { - return rewrite_host_config_; - } - - private: - std::string instance_; - std::string self_namespace_; - bool add_origin_source_; - int32_t max_logging_body_size_per_reqeuest_; - - std::vector rewrite_host_config_; +public: + explicit GressFilterConfig(const GressPbConfig& config); + const std::string& instance() const { return instance_; } + + const std::string& selfNamespace() const { return self_namespace_; } + + bool addOriginSource() const { return add_origin_source_; } + + int32_t maxLoggingBodySizePerReqeuest() { return max_logging_body_size_per_reqeuest_; } + + const std::vector& rewriteHostConfig() const { return rewrite_host_config_; } + +private: + std::string instance_; + std::string self_namespace_; + bool add_origin_source_; + int32_t max_logging_body_size_per_reqeuest_; + + std::vector rewrite_host_config_; }; using GressFilterConfigSharedPtr = std::shared_ptr; - class GressFilter : public Envoy::Http::PassThroughFilter, - public Logger::Loggable { - public: - explicit GressFilter(GressFilterConfigSharedPtr config) : - config_(config), - host_(), - request_id_(), - record_request_body_(false), + public Logger::Loggable { +public: + explicit GressFilter(GressFilterConfigSharedPtr config) + : config_(config), host_(), request_id_(), record_request_body_(false), record_response_body_(false) {} - Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, - bool) override; - Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override; + Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; + + Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, + bool end_stream) override; + Http::FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) override; + + Http::FilterTrailersStatus encodeTrailers(Http::ResponseTrailerMap& headers) override; + +private: + bool rewriteHost(Http::RequestHeaderMap& headers); + bool rewriteHost(Http::RequestHeaderMap& headers, const RewriteHostConfig& rh); + bool recordBody(Buffer::OwnedImpl& body, Buffer::Instance& data, bool end_stream, bool is_req); - Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, - bool end_stream) override; - Http::FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) override; + std::string getRichMessage(const StreamInfo::StreamInfo& stream_info, + const std::string& error_message, bool formatted); - private: - bool rewriteHost(Http::RequestHeaderMap& headers); - bool rewriteHost(Http::RequestHeaderMap& headers, const RewriteHostConfig& rh); - bool recordBody(Buffer::OwnedImpl& body, Buffer::Instance& data, bool end_stream, bool is_req); + GressFilterConfigSharedPtr config_; + std::string host_; + std::string request_id_; - GressFilterConfigSharedPtr config_; - std::string host_; - std::string request_id_; + bool record_request_body_; + bool record_response_body_; - bool record_request_body_; - bool record_response_body_; - Buffer::OwnedImpl req_body_; - Buffer::OwnedImpl resp_body_; + Buffer::OwnedImpl req_body_; + Buffer::OwnedImpl resp_body_; }; } // namespace KusciaGress diff --git a/kuscia/source/filters/http/kuscia_poller/BUILD b/kuscia/source/filters/http/kuscia_poller/BUILD new file mode 100755 index 0000000..508e4c1 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/BUILD @@ -0,0 +1,47 @@ +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", +) + +package(default_visibility = ["//visibility:public"]) + +envoy_cc_library( + name = "poller_filter", + srcs = [ + "callbacks.cc", + "poller_filter.cc", + ], + hdrs = [ + "common.h", + "callbacks.h", + "poller_filter.h", + ], + repository = "@envoy", + deps = [ + "//kuscia/api/filters/http/kuscia_poller/v3:pkg_cc_proto", + "//kuscia/api/filters/http/kuscia_receiver/v3:pkg_cc_proto", + "//kuscia/source/filters/http/kuscia_common", + "@envoy//source/common/buffer:buffer_lib", + "@envoy//source/common/common:base64_lib", + "@envoy//source/common/http:header_utility_lib", + "@envoy//source/common/common:assert_lib", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + ], +) + +envoy_cc_library( + name = "kuscia_poller_config", + srcs = [ + "config.cc", + ], + hdrs = [ + "common.h", + "config.h", + ], + repository = "@envoy", + deps = [ + ":poller_filter", + "@envoy//envoy/registry", + "@envoy//source/extensions/filters/http/common:factory_base_lib", + ], +) diff --git a/kuscia/source/filters/http/kuscia_poller/callbacks.cc b/kuscia/source/filters/http/kuscia_poller/callbacks.cc new file mode 100644 index 0000000..0be7208 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/callbacks.cc @@ -0,0 +1,245 @@ +// Copyright 2024 Ant Group Co., 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 "kuscia/source/filters/http/kuscia_poller/callbacks.h" +#include "source/common/http/utility.h" +#include "source/common/http/message_impl.h" +#include "source/common/http/headers.h" +#include "callbacks.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaPoller { + +bool replyToReceiver(const std::string& conn_id, Upstream::ClusterManager& cluster_manager, const std::string& msg_id, const std::string& host, const ResponseMessagePb& resp_msg_pb, int timeout, std::string &errmsg) +{ + // Ensure the existence of the target cluster + std::string cluster_name = "internal-cluster"; + Upstream::ThreadLocalCluster* cluster = cluster_manager.getThreadLocalCluster(cluster_name); + if (cluster == nullptr) { + errmsg = "cluster " + cluster_name + " not found"; + return false; + } + + // Get asynchronous HTTP client + Http::AsyncClient& client = cluster->httpAsyncClient(); + + // Construct request message + Http::RequestMessagePtr req_msg(new Http::RequestMessageImpl()); + req_msg->headers().setPath("/reply?msgid=" + msg_id); + req_msg->headers().setHost(host); + req_msg->headers().setReferenceMethod(Http::Headers::get().MethodValues.Post); + req_msg->headers().setReferenceContentType(Envoy::Http::Headers::get().ContentTypeValues.Protobuf); + + std::string serialized_data = resp_msg_pb.SerializeAsString(); + + req_msg->body().add(serialized_data.data(), serialized_data.size()); + + // Send asynchronous requests + ReceiverCallbacks* callbacks = new ReceiverCallbacks(conn_id, msg_id); + Envoy::Http::AsyncClient::RequestOptions options; + options.setTimeout(std::chrono::milliseconds(timeout * 1000)); + Http::AsyncClient::Request* request = client.send(std::move(req_msg), *callbacks, options); + if (request == nullptr) { + delete callbacks; + callbacks = nullptr; + errmsg = "can't create request"; + return false; + } + + return true; +} + +ApplicationCallbacks::~ApplicationCallbacks() +{ + ENVOY_LOG(debug, "[{}] ApplicationCallbacks destroyed, message id: {}", conn_id_, message_id_); +} + +void KusciaPoller::ApplicationCallbacks::onSuccess(const Http::AsyncClient::Request &, Http::ResponseMessagePtr &&response) +{ + replyToReceiverOnSuccess(std::move(response)); + delete this; +} + +void ApplicationCallbacks::replyToReceiverOnSuccess(Http::ResponseMessagePtr&& response) +{ + Http::ResponseHeaderMap& headers = response->headers(); + const uint64_t status_code = Http::Utility::getResponseStatus(headers); + if (status_code == 200) { + ENVOY_LOG(info, "[{}] Forward request message {} successully, status code: {}", conn_id_, message_id_, status_code); + } else { + ENVOY_LOG(warn, "[{}] Forward request message {} , status code: {}", conn_id_, message_id_, status_code); + } + + ResponseMessagePb resp_msg_pb; + headers.iterate([&resp_msg_pb](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { + (*resp_msg_pb.mutable_headers())[std::string(header.key().getStringView())] = std::string(header.value().getStringView()); + return Envoy::Http::HeaderMap::Iterate::Continue; + }); + resp_msg_pb.set_status_code(status_code); + resp_msg_pb.set_body(response->body().toString()); + resp_msg_pb.set_end_stream(true); + + std::string errmsg; + if (!replyToReceiver(conn_id_, cluster_manager_, message_id_, receiver_host_, resp_msg_pb, rsp_timeout_, errmsg)) { + ENVOY_LOG(error, "[{}] Reply to receiver error: {}, message id: {}", conn_id_, message_id_, errmsg); + } +} + +void ApplicationCallbacks::onFailure(const Http::AsyncClient::Request &, Http::AsyncClient::FailureReason) +{ + ENVOY_LOG(error, "[{}] Forward request message {} error: network error", conn_id_, message_id_); + replyToReceiverOnFailure(); + delete this; +} + +void ApplicationCallbacks::replyToReceiverOnFailure() +{ + ResponseMessagePb resp_msg_pb; + resp_msg_pb.set_status_code(502); + resp_msg_pb.set_end_stream(true); + std::string errmsg; + if (!replyToReceiver(conn_id_, cluster_manager_, message_id_, receiver_host_, resp_msg_pb, rsp_timeout_, errmsg)) { + ENVOY_LOG(error, "[{}] Reply to receiver error: {}, message id: {}", conn_id_, message_id_, errmsg); + } +} + +KusciaPoller::ApiserverCallbacks::~ApiserverCallbacks() +{ + ENVOY_LOG(info, "[{}] ApiserverCallbacks destroyed, message id: {}", conn_id_, message_id_); +} + +void ApiserverCallbacks::onHeaders(Http::ResponseHeaderMapPtr &&headers, bool end_stream) +{ + ENVOY_LOG(info, "[{}] ApiserverCallbacks onHeaders, message id: {}", conn_id_, message_id_); + if (headers == nullptr) { + ENVOY_LOG(error, "[{}] Headers is null, message id: {}", conn_id_, message_id_); + return; + } + + const uint64_t status_code = Http::Utility::getResponseStatus(*headers); + if (status_code == 200) { + ENVOY_LOG(info, "[{}] Forward request message {} successully, status code: {}", conn_id_, message_id_, status_code); + } else { + ENVOY_LOG(warn, "[{}] Forward request message {} , status code: {}", conn_id_, message_id_, status_code); + } + + ResponseMessagePb resp_msg_pb; + headers->iterate([&resp_msg_pb](const Http::HeaderEntry& header) -> Http::HeaderMap::Iterate { + (*resp_msg_pb.mutable_headers())[std::string(header.key().getStringView())] = std::string(header.value().getStringView()); + return Envoy::Http::HeaderMap::Iterate::Continue; + }); + resp_msg_pb.set_status_code(status_code); + resp_msg_pb.set_chunk_data(true); + resp_msg_pb.set_index(index_++); + if (end_stream) { + resp_msg_pb.set_end_stream(true); + } + + std::string errmsg; + if (!replyToReceiver(conn_id_, cluster_manager_, message_id_, receiver_host_, resp_msg_pb, rsp_timeout_, errmsg)) { + ENVOY_LOG(error, "[{}] Reply to receiver error: {}, message id: {}", conn_id_, message_id_, errmsg); + } +} + +void ApiserverCallbacks::onData(Buffer::Instance &data, bool end_stream) +{ + ENVOY_LOG(info, "[{}] ApiserverCallbacks onData, message id: {}, data len: {}", conn_id_, message_id_, data.length()); + ResponseMessagePb resp_msg_pb; + + resp_msg_pb.set_body(data.toString()); + resp_msg_pb.set_chunk_data(true); + resp_msg_pb.set_index(index_++); + if (end_stream) { + resp_msg_pb.set_end_stream(true); + } + + std::string errmsg; + if (!replyToReceiver(conn_id_, cluster_manager_, message_id_, receiver_host_, resp_msg_pb, rsp_timeout_, errmsg)) { + ENVOY_LOG(error, "[{}] Reply to receiver error: {}, message id: {}", conn_id_, message_id_, errmsg); + } +} + +void ApiserverCallbacks::onTrailers(Http::ResponseTrailerMapPtr &&) +{ + ENVOY_LOG(info, "[{}] ApiserverCallbacks onTrailers, message id: {}", conn_id_, message_id_); + ResponseMessagePb resp_msg_pb; + resp_msg_pb.set_end_stream(true); + resp_msg_pb.set_chunk_data(true); + resp_msg_pb.set_index(index_++); + + std::string errmsg; + if (!replyToReceiver(conn_id_, cluster_manager_, message_id_, receiver_host_, resp_msg_pb, rsp_timeout_, errmsg)) { + ENVOY_LOG(error, "[{}] Reply to receiver error: {}, message id: {}", conn_id_, message_id_, errmsg); + } +} + +void ApiserverCallbacks::onReset() +{ + ENVOY_LOG(info, "[{}] ApiserverCallbacks onReset, message id: {}", conn_id_, message_id_); + ResponseMessagePb resp_msg_pb; + resp_msg_pb.set_status_code(503); + resp_msg_pb.set_end_stream(true); + resp_msg_pb.set_index(index_++); + + std::string errmsg; + if (!replyToReceiver(conn_id_, cluster_manager_, message_id_, receiver_host_, resp_msg_pb, rsp_timeout_, errmsg)) { + ENVOY_LOG(error, "[{}] Reply to receiver error: {}, message id: {}", conn_id_, message_id_, errmsg); + } + + delete this; +} + + +void ApiserverCallbacks::onComplete() +{ + ENVOY_LOG(info, "[{}] ApiserverCallbacks onComplete, message id: {}", conn_id_, message_id_); + + delete this; +} + +void ApiserverCallbacks::saveRequestMessage(Http::RequestMessagePtr &&req_message) +{ + req_message_ = std::move(req_message); +} + +ReceiverCallbacks::~ReceiverCallbacks() +{ + ENVOY_LOG(debug, "[{}] ReceiverCallbacks destroyed, message id: {}", conn_id_, msg_id_); +} + +void ReceiverCallbacks::onSuccess(const Http::AsyncClient::Request &, Http::ResponseMessagePtr &&response) +{ + const uint64_t status_code = Http::Utility::getResponseStatus(response->headers()); + if (status_code == 200) { + ENVOY_LOG(info, "[{}] Forward response message {} successully, status code: {}", conn_id_, msg_id_, status_code); + } else { + ENVOY_LOG(warn, "[{}] Forward response message {} , status code: {}", conn_id_, msg_id_, status_code); + } + + delete this; +} + +void ReceiverCallbacks::onFailure(const Http::AsyncClient::Request &, Http::AsyncClient::FailureReason) +{ + ENVOY_LOG(error, "[{}] Forward response message {} error: {}", conn_id_, msg_id_, "network error"); + delete this; +} + +} +// namespace KusciaPoller +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_poller/callbacks.h b/kuscia/source/filters/http/kuscia_poller/callbacks.h new file mode 100644 index 0000000..b0743b9 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/callbacks.h @@ -0,0 +1,93 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "kuscia/source/filters/http/kuscia_poller/common.h" +#include "envoy/http/async_client.h" +#include "source/common/common/logger.h" +#include "envoy/upstream/cluster_manager.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaPoller { + +extern bool replyToReceiver(const std::string& conn_id, Upstream::ClusterManager& cluster_manager, const std::string& msg_id, const std::string& host, const ResponseMessagePb& resp_msg_pb, int timeout, std::string &errmsg); + +class ApplicationCallbacks : public Http::AsyncClient::Callbacks, public Logger::Loggable { +public: + ApplicationCallbacks(const std::string& conn_id, Upstream::ClusterManager& cluster_manager, const std::string& message_id, const std::string& peer_receiver_host, int rsp_timeout) + : conn_id_(conn_id), cluster_manager_(cluster_manager), message_id_(message_id), receiver_host_(peer_receiver_host), rsp_timeout_(rsp_timeout) {} + ~ApplicationCallbacks(); + + void onSuccess(const Http::AsyncClient::Request& request, Http::ResponseMessagePtr&& response) override; + void onFailure(const Http::AsyncClient::Request& request, Http::AsyncClient::FailureReason reason) override; + void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} + void replyToReceiverOnSuccess(Http::ResponseMessagePtr&& response); + void replyToReceiverOnFailure(); + +private: + std::string conn_id_; + Upstream::ClusterManager& cluster_manager_; + std::string message_id_; + std::string receiver_host_; + int rsp_timeout_; +}; + +class ApiserverCallbacks : public Http::AsyncClient::StreamCallbacks, public Logger::Loggable { +public: + ApiserverCallbacks(const std::string& conn_id, Upstream::ClusterManager& cluster_manager, const std::string& message_id, const std::string& peer_receiver_host, int rsp_timeout) + : conn_id_(conn_id), cluster_manager_(cluster_manager), message_id_(message_id), receiver_host_(peer_receiver_host), rsp_timeout_(rsp_timeout), index_(0) {} + ~ApiserverCallbacks(); + + void onHeaders(Http::ResponseHeaderMapPtr&& headers, bool end_stream) override; + void onData(Buffer::Instance& data, bool end_stream) override; + void onTrailers(Http::ResponseTrailerMapPtr&& trailers) override; + void onReset() override; + void onComplete() override; + + void replyToReceiverOnSuccess(Http::ResponseMessagePtr&& response); + void replyToReceiverOnFailure(); + + void saveRequestMessage(Http::RequestMessagePtr&& req_message); + +private: + std::string conn_id_; + // Http::RequestHeaderMapPtr headers_; + Http::RequestMessagePtr req_message_; + Upstream::ClusterManager& cluster_manager_; + std::string message_id_; + std::string receiver_host_; + int rsp_timeout_; + int index_; +}; + +class ReceiverCallbacks : public Http::AsyncClient::Callbacks, public Logger::Loggable { +public: + ReceiverCallbacks(const std::string& conn_id, const std::string& msg_id) : conn_id_(conn_id), msg_id_(msg_id) {} + ~ReceiverCallbacks(); + void onSuccess(const Http::AsyncClient::Request& request, Http::ResponseMessagePtr&& response) override; + void onFailure(const Http::AsyncClient::Request& request, Http::AsyncClient::FailureReason reason) override; + void onBeforeFinalizeUpstreamSpan(Tracing::Span&, const Http::ResponseHeaderMap*) override {} + +private: + std::string conn_id_; + std::string msg_id_; +}; + +} // namespace KusciaPoller +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_poller/common.h b/kuscia/source/filters/http/kuscia_poller/common.h new file mode 100644 index 0000000..41eefca --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/common.h @@ -0,0 +1,34 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "kuscia/api/filters/http/kuscia_poller/v3/poller.pb.h" +#include "kuscia/api/filters/http/kuscia_receiver/v3/message.pb.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaPoller { + +using PollerConfigPbConfig = envoy::extensions::filters::http::kuscia_poller::v3::Poller; +using RequestMessagePb = envoy::extensions::filters::http::kuscia_receiver::v3::RequestMessage; +using ResponseMessagePb = envoy::extensions::filters::http::kuscia_receiver::v3::ResponseMessage; + +static const std::string ReceiverService = "msg-receiver"; + +} // namespace KusciaPoller +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_poller/config.cc b/kuscia/source/filters/http/kuscia_poller/config.cc new file mode 100755 index 0000000..8b98fad --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/config.cc @@ -0,0 +1,42 @@ +// Copyright 2024 Ant Group Co., 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 "kuscia/source/filters/http/kuscia_poller/config.h" + +#include "envoy/registry/registry.h" + +#include "kuscia/source/filters/http/kuscia_poller/poller_filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaPoller { + +Http::FilterFactoryCb PollerConfigFactory::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::kuscia_poller::v3::Poller& proto_config, + const std::string&, + Server::Configuration::FactoryContext& context) { + + return [proto_config, &context](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared(proto_config, context.clusterManager(), context.timeSource())); + }; +} + +REGISTER_FACTORY(PollerConfigFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace KusciaPoller +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_poller/config.h b/kuscia/source/filters/http/kuscia_poller/config.h new file mode 100755 index 0000000..76c612f --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/config.h @@ -0,0 +1,43 @@ +// Copyright 2024 Ant Group Co., 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. + + +#pragma once + +#include + +#include "source/extensions/filters/http/common/factory_base.h" + +#include "kuscia/api/filters/http/kuscia_poller/v3/poller.pb.h" +#include "kuscia/api/filters/http/kuscia_poller/v3/poller.pb.validate.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaPoller { + +class PollerConfigFactory : public Extensions::HttpFilters::Common::FactoryBase { + public: + PollerConfigFactory() : FactoryBase("envoy.filters.http.kuscia_poller") {} + + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::kuscia_poller::v3::Poller&, + const std::string&, + Server::Configuration::FactoryContext&) override; +}; + +} // namespace KusciaPoller +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_poller/poller_filter.cc b/kuscia/source/filters/http/kuscia_poller/poller_filter.cc new file mode 100644 index 0000000..54d4a6a --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/poller_filter.cc @@ -0,0 +1,332 @@ +// Copyright 2024 Ant Group Co., 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 "kuscia/source/filters/http/kuscia_poller/poller_filter.h" +#include "kuscia/source/filters/http/kuscia_poller/callbacks.h" +#include "kuscia/source/filters/http/kuscia_common/kuscia_header.h" +#include "envoy/http/filter.h" +#include "envoy/http/header_map.h" +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include "source/common/http/utility.h" +#include "poller_filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaPoller { + +bool starts_with(absl::string_view str, absl::string_view prefix) { + return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix; +} + +// isPollRequest checks if a host string matches the format of the three part domain name "receiver.*.svc and a path string matches the format "/poll*" +bool isPollRequest(absl::string_view host, absl::string_view path, const std::string expected_service_name, std::string &domain_id) { + if (host.size() < 13) { // "receiver..svc" is 13 characters long + return false; + } + + // Split the host into segments based on '.' + std::vector segments; + size_t start = 0; + size_t end = host.find('.'); + while (end != absl::string_view::npos) { + segments.push_back(host.substr(start, end - start)); + start = end + 1; + end = host.find('.', start); + } + segments.push_back(host.substr(start, end)); + + // Check if the host has exactly 3 segments, starts with "receiver", and ends with "svc" + if (!(segments.size() == 3 && segments[0] == expected_service_name && segments[2] == "svc")) { + return false; + } + // Check if the path starts with "/poll" + if (!starts_with(path, "/poll")) { + return false; + } + + domain_id = std::string(segments[1]); + + return true; +} + +PollerFilter::PollerFilter(const PollerConfigPbConfig &config, Upstream::ClusterManager & cluster_manager, TimeSource& time_source) + : receiver_service_name_(config.receiver_service_name()), req_timeout_(config.request_timeout()), rsp_timeout_(config.response_timeout()), + heartbeat_interval_(config.heartbeat_interval()), cluster_manager_(cluster_manager), time_source_(time_source) +{ + if (receiver_service_name_.size() == 0) { + receiver_service_name_ = "receiver"; + } + if (req_timeout_ <= 0) { + req_timeout_ = 30; + } + if (rsp_timeout_ <= 0) { + rsp_timeout_ = 30; + } + if (heartbeat_interval_ <= 0) { + heartbeat_interval_ = 25; + } + + for (const auto& source_headers : config.append_headers()) { + std::vector> headers; + headers.reserve(source_headers.headers_size()); + for (const auto& entry : source_headers.headers()) { + headers.emplace_back(entry.key(), entry.value()); + } + append_headers_.emplace(source_headers.source(), headers); + } +} + +PollerFilter::~PollerFilter() +{ + if (response_timer_) { + response_timer_->disableTimer(); + } +} + +Http::FilterHeadersStatus PollerFilter::decodeHeaders(Http::RequestHeaderMap &headers, bool) +{ + auto host = headers.getHostValue(); + auto path = headers.getPathValue(); + + if (isPollRequest(host, path, receiver_service_name_, peer_domain_)) + { + auto query_params = Http::Utility::parseQueryString(path); + auto svc = query_params.find(KusciaCommon::ServiceParamKey); + + Envoy::SystemTime system_time = time_source_.systemTime(); + std::chrono::seconds seconds_since_epoch = std::chrono::duration_cast( + system_time.time_since_epoch()); + std::string current_timestamp = std::to_string(seconds_since_epoch.count()); + if (svc == query_params.end() && svc->second.empty()) { + conn_id_ = "unknown:" + peer_domain_ + ":" + current_timestamp; + } else { + conn_id_ = std::string(svc->second) + ":" + peer_domain_ + ":" + current_timestamp; + } + peer_receiver_host_ = receiver_service_name_ + "." + peer_domain_ + ".svc"; + forward_response_ = true; + ENVOY_LOG(info, "[{}] Poller begin to forward response, host: {}, path: {}, peer_domain_: {}", conn_id_, host, path, peer_domain_); + } + + return Http::FilterHeadersStatus::Continue; +} + +Http::FilterHeadersStatus PollerFilter::encodeHeaders(Http::ResponseHeaderMap &headers, bool) +{ + if (forward_response_) + { + const auto status = headers.getStatusValue(); + ENVOY_LOG(info, "[{}] Poller status: {}", conn_id_, status); + + if (status == "200") + { + encoder_callbacks_->setEncoderBufferLimit(1024 * 1024 * 100); + response_timer_ = encoder_callbacks_->dispatcher().createTimer([this]() -> void { + sendHeartbeat(); + }); + response_timer_->enableTimer(std::chrono::seconds(heartbeat_interval_)); + } else { + forward_response_ = false; + } + } + + return Http::FilterHeadersStatus::Continue; +} + +Http::FilterDataStatus PollerFilter::encodeData(Buffer::Instance &data, bool) +{ + if (!forward_response_) + { + return Http::FilterDataStatus::Continue; + } + + while (attemptToDecodeMessage(data)){} + + return Http::FilterDataStatus::StopIterationNoBuffer; +} + +bool PollerFilter::attemptToDecodeMessage(Buffer::Instance &data) +{ + if (data.length() == 0) { + return false; + } + + RequestMessagePb message; + KusciaCommon::DecodeStatus status = decoder_.decode(data, message); + if (status == KusciaCommon::DecodeStatus::Ok) { + std::string errmsg; + int32_t status_code = forwardMessage(message, errmsg); + if (status_code != 200) { + ENVOY_LOG(error, "[{}] Forward message {} to {}{} error: {}", conn_id_, message.id(), message.host(), message.path(), errmsg); + + ResponseMessagePb resp_msg_pb; + resp_msg_pb.set_status_code(status_code); + resp_msg_pb.set_end_stream(true); + std::string errmsg; + if (!replyToReceiver(conn_id_, cluster_manager_, message.id(), peer_receiver_host_, resp_msg_pb, rsp_timeout_, errmsg)) { + ENVOY_LOG(error, "[{}] Reply to receiver error: {}, message id: {}", conn_id_, message.id(), errmsg); + } + } + return true; + } else if (status == KusciaCommon::DecodeStatus::NeedMoreData) { + ENVOY_LOG(info, "[{}] Decode message need more data", conn_id_); + return false; + } else { + ENVOY_LOG(error, "[{}] Decode message error code: {}", conn_id_, KusciaCommon::decodeStatusString(status)); + encoder_callbacks_->resetStream(); + return false; + } + + return false; +} + +bool parseKusciaHost(const std::string& host, std::string &service) { + // Split the host into segments based on '.' + std::vector segments; + size_t start = 0; + size_t end = host.find('.'); + while (end != std::string::npos) { + segments.push_back(host.substr(start, end - start)); + start = end + 1; + end = host.find('.', start); + } + segments.push_back(host.substr(start, end)); + + if (segments.size() < 1) { + return false; + } + + service = segments[0]; + + return true; +} + +int32_t PollerFilter::forwardMessage(const RequestMessagePb &message, std::string& errmsg) +{ + std::string host = message.host(); + + ENVOY_LOG(info, "[{}] Forward message {} to {}{}, method: {}", conn_id_, message.id(), message.host(), message.path(), message.method()); + + std::string service_name; + if (!parseKusciaHost(host, service_name)) { + errmsg = "parse kuscia host " + host + " error"; + return 500; + } + std::string cluster_name = "service-" + service_name; + // TODO check service_name + if (message.path() == "/handshake") { + cluster_name = "handshake-cluster"; + } + + // Ensure the existence of the target cluster + // TODO Not considering domain transit + Upstream::ThreadLocalCluster* cluster = cluster_manager_.getThreadLocalCluster(cluster_name); + if (cluster == nullptr) { + errmsg = "cluster " + cluster_name + " not found"; + return 404; + } + + // Get asynchronous HTTP client + Http::AsyncClient& client = cluster->httpAsyncClient(); + + // Construct request message + Http::RequestMessagePtr req_msg(new Http::RequestMessageImpl()); + + for (const auto& header : message.headers()) { + // Add each header to the request's headers + req_msg->headers().addCopy( + Envoy::Http::LowerCaseString(header.first), + header.second + ); + } + + req_msg->headers().setPath(message.path()); + req_msg->headers().setHost(message.host()); + req_msg->headers().setMethod(message.method()); + req_msg->body().add(message.body()); + + if (service_name == "apiserver") { + return forwardToApiserver(client, req_msg, message.id(), errmsg); + } else { + return forwardToApplication(client, req_msg, message.id(), errmsg); + } +} + +int32_t PollerFilter::forwardToApplication(Http::AsyncClient& client, Http::RequestMessagePtr& req_msg, const std::string& msg_id, std::string& errmsg) +{ + // Send asynchronous requests + ApplicationCallbacks* callbacks = new ApplicationCallbacks(conn_id_, cluster_manager_, msg_id, peer_receiver_host_, rsp_timeout_); + Envoy::Http::AsyncClient::RequestOptions options; + options.setTimeout(std::chrono::milliseconds(req_timeout_ * 1000)); + Http::AsyncClient::Request* request = client.send(std::move(req_msg), *callbacks, options); + if (request == nullptr) { + delete callbacks; + callbacks = nullptr; + errmsg = "can't create request"; + return 500; + } + + return 200; +} + +int32_t PollerFilter::forwardToApiserver(Http::AsyncClient& client, Http::RequestMessagePtr& req_msg, const std::string& msg_id, std::string& errmsg) +{ + // TODO Not considering domain transit + appendHeaders(req_msg->headers()); + + // Send asynchronous requests + ApiserverCallbacks* callbacks = new ApiserverCallbacks(conn_id_, cluster_manager_, msg_id, peer_receiver_host_, rsp_timeout_); + Envoy::Http::AsyncClient::StreamOptions options; + options.setTimeout(std::chrono::milliseconds(6 * 60 * 1000)); + + // TODO How to know if the client connection is released + Envoy::Http::AsyncClient::Stream* stream = client.start(*callbacks, options); + if (!stream) { + delete callbacks; + callbacks = nullptr; + errmsg = "can't create stream request"; + return 500; + } + + stream->sendHeaders(req_msg->headers(), false); + + stream->sendData(req_msg->body(), true); + + callbacks->saveRequestMessage(std::move(req_msg)); + + return 200; +} + +void PollerFilter::appendHeaders(Http::RequestHeaderMap& headers) { + auto iter = append_headers_.find(peer_domain_); + if (iter != append_headers_.end()) { + for (const auto& entry : iter->second) { + headers.addCopy(Http::LowerCaseString(entry.first), entry.second); + } + } +} + +void PollerFilter::sendHeartbeat() { + Buffer::OwnedImpl hello_data("hello"); + encoder_callbacks_->injectEncodedDataToFilterChain(hello_data, false); + + response_timer_->enableTimer(std::chrono::seconds(heartbeat_interval_)); +} + + +} // namespace KusciaPoller +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_poller/poller_filter.h b/kuscia/source/filters/http/kuscia_poller/poller_filter.h new file mode 100644 index 0000000..9c54049 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_poller/poller_filter.h @@ -0,0 +1,80 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "kuscia/source/filters/http/kuscia_common/coder.h" +#include "kuscia/source/filters/http/kuscia_poller/common.h" +#include "source/common/common/logger.h" +#include "envoy/http/filter.h" +#include "envoy/upstream/cluster_manager.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" +#include "kuscia/source/filters/http/kuscia_poller/callbacks.h" +#include "envoy/event/timer.h" +#include "envoy/common/time.h" +#include +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaPoller { + +class PollerFilter : public Http::PassThroughFilter, public Logger::Loggable { +public: + explicit PollerFilter(const PollerConfigPbConfig& config, Upstream::ClusterManager& cluster_manager, TimeSource& time_source); + ~PollerFilter(); + + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, bool) override; + + // Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; + + Http::FilterHeadersStatus encodeHeaders(Http::ResponseHeaderMap& headers, bool end_stream) override; + + Http::FilterDataStatus encodeData(Buffer::Instance& data, bool end_stream) override; + +private: + bool attemptToDecodeMessage(Buffer::Instance& data); + + int32_t forwardMessage(const RequestMessagePb &message, std::string& errmsg); + int32_t forwardToApplication(Http::AsyncClient& client, Http::RequestMessagePtr& req_msg, const std::string& msg_id, std::string& errmsg); + int32_t forwardToApiserver(Http::AsyncClient& client, Http::RequestMessagePtr& req_msg, const std::string& msg_id, std::string& errmsg); + + void appendHeaders(Http::RequestHeaderMap& headers); + void sendHeartbeat(); + + bool forward_response_{false}; + std::string conn_id_; + std::string peer_domain_; + std::string receiver_service_name_; + std::string peer_receiver_host_; + int req_timeout_; + int rsp_timeout_; + int heartbeat_interval_; + Upstream::ClusterManager& cluster_manager_; + KusciaCommon::Decoder decoder_; + + std::map>, std::less<>> append_headers_; + + Http::RequestHeaderMapPtr headers_; + + Event::TimerPtr response_timer_; + TimeSource& time_source_; +}; + + +} // namespace KusciaPoller +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_receiver/BUILD b/kuscia/source/filters/http/kuscia_receiver/BUILD new file mode 100644 index 0000000..5e83fc5 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/BUILD @@ -0,0 +1,60 @@ +load( + "@envoy//bazel:envoy_build_system.bzl", + "envoy_cc_library", +) + +package(default_visibility = ["//visibility:public"]) + +envoy_cc_library( + name = "receiver_filter", + srcs = [ + "receiver_filter.cc", + ], + hdrs = [ + "receiver_filter.h", + "event_loop.h", + "conn.h", + "svc_register.h", + "event.h", + ], + repository = "@envoy", + deps = [ + "//kuscia/api/filters/http/kuscia_receiver/v3:pkg_cc_proto", + "//kuscia/source/filters/http/kuscia_common", + "@envoy//source/common/buffer:buffer_lib", + "@envoy//source/common/common:base64_lib", + "@envoy//source/common/http:header_utility_lib", + "@envoy//source/common/http:headers_lib", + "@envoy//source/common/http:message_lib", + "@envoy//source/common/common:assert_lib", + "@envoy//source/extensions/filters/http/common:pass_through_filter_lib", + "@envoy//source/common/http:codes_lib", + "@envoy//source/common/http:utility_lib", + "@com_github_nlohmann_json//:json", + "@envoy//source/common/api:os_sys_calls_lib", + "@envoy//envoy/network:connection_interface", + "@envoy//source/common/network:connection_lib", + "@envoy//source/common/common:random_generator_lib", + "@envoy//source/common/upstream:cluster_manager_lib", + "@envoy//envoy/http:async_client_interface", + "@envoy//source/extensions/filters/http/common:factory_base_lib", + "@envoy//source/common/common:hex_lib", + # "@envoy//source/extensions/tracers/zipkin:zipkin_lib", + ], + external_deps = [ + "curl", + "http_parser", + ], +) + +envoy_cc_library( + name = "kuscia_receiver_config", + srcs = ["config.cc"], + hdrs = ["config.h"], + repository = "@envoy", + deps = [ + ":receiver_filter", + "@envoy//envoy/registry", + "@envoy//source/extensions/filters/http/common:factory_base_lib", + ], +) diff --git a/kuscia/source/filters/http/kuscia_receiver/config.cc b/kuscia/source/filters/http/kuscia_receiver/config.cc new file mode 100644 index 0000000..04bb188 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/config.cc @@ -0,0 +1,39 @@ +// Copyright 2024 Ant Group Co., 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 "kuscia/source/filters/http/kuscia_receiver/config.h" +#include "envoy/registry/registry.h" +#include "kuscia/source/filters/http/kuscia_receiver/receiver_filter.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +Http::FilterFactoryCb ReceiverConfigFactory::createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::kuscia_receiver::v3::Receiver& proto_config, + const std::string&, Server::Configuration::FactoryContext& context) { + ReceiverFilterConfigSharedPtr config = + std::make_shared(proto_config, context); + return [config](Http::FilterChainFactoryCallbacks& callbacks) -> void { + callbacks.addStreamFilter(std::make_shared(config)); + }; +} + +REGISTER_FACTORY(ReceiverConfigFactory, Server::Configuration::NamedHttpFilterConfigFactory); + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_receiver/config.h b/kuscia/source/filters/http/kuscia_receiver/config.h new file mode 100644 index 0000000..f172a3e --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/config.h @@ -0,0 +1,40 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.h" +#include "kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.validate.h" +#include "source/extensions/filters/http/common/factory_base.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +class ReceiverConfigFactory + : public Extensions::HttpFilters::Common::FactoryBase< + envoy::extensions::filters::http::kuscia_receiver::v3::Receiver> { +public: + ReceiverConfigFactory() : FactoryBase("envoy.filters.http.kuscia_receiver") {} + + Http::FilterFactoryCb createFilterFactoryFromProtoTyped( + const envoy::extensions::filters::http::kuscia_receiver::v3::Receiver&, const std::string&, + Server::Configuration::FactoryContext&) override; +}; + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_receiver/conn.h b/kuscia/source/filters/http/kuscia_receiver/conn.h new file mode 100644 index 0000000..6e7dedb --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/conn.h @@ -0,0 +1,314 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "event.h" +#include "source/common/buffer/buffer_impl.h" +#include "source/common/common/hex.h" +#include "source/common/common/logger.h" +#include "source/common/http/header_map_impl.h" +#include "source/common/http/headers.h" +#include "source/common/http/utility.h" +#include "source/common/network/connection_impl.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +#define ASSERT_STREAM_LOG_RETURN(cond) \ + do { \ + if (cond) { \ + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] before {} L{}, stream destroyed", conn_id_, \ + stream_id_, __FUNCTION__, __LINE__); \ + return; \ + } else { \ + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] before {} L{}, stream ok", conn_id_, stream_id_, \ + __FUNCTION__, __LINE__); \ + } \ + } while (0) + +#define ASSERT_STREAM_LOG_RETURN_VAL(cond, retval) \ + do { \ + if (cond) { \ + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] before {} L{}, stream destroyed", conn_id_, \ + stream_id_, __FUNCTION__, __LINE__); \ + return retval; \ + } else { \ + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] before {} L{}, stream ok", conn_id_, stream_id_, \ + __FUNCTION__, __LINE__); \ + } \ + } while (0) + +using BufferPtr = std::shared_ptr; + +class TcpConn : public Logger::Loggable { +public: + explicit TcpConn(Http::StreamDecoderFilterCallbacks* cbs, StreamDestroyPtr sd) + : cbs_(cbs), sd_(sd), delayed_(false) { + if (sd_->load(std::memory_order_relaxed)) { + delayed_ = true; + return; + } + conn_id_ = cbs_->connection().ptr()->id(); + dispatcher_ = &cbs->dispatcher(); + stream_id_ = cbs->streamId(); + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] constructor, active tcp conn count {}", conn_id_, + stream_id_, active_conn_.fetch_add(1, std::memory_order_relaxed) + 1); + } + + ~TcpConn() { + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] deconstructor, active tcp conn count {}", conn_id_, + stream_id_, active_conn_.fetch_add(-1, std::memory_order_relaxed) - 1); + } + + bool writeRequestHeader() { + + ASSERT_STREAM_LOG_RETURN_VAL(sd_->load(std::memory_order_relaxed), true); + + dispatcher_->post( + [cbs = cbs_, sd = sd_, conn_id_ = conn_id_, stream_id_ = stream_id_]() mutable { + ASSERT_STREAM_LOG_RETURN(sd->load(std::memory_order_relaxed)); + Http::ResponseHeaderMapPtr headers = Http::ResponseHeaderMapImpl::create(); + headers->setTransferEncoding(Http::Headers::get().TransferEncodingValues.Chunked); + headers->setStatus(200); + cbs->encodeHeaders(std::move(headers), false, REPLY_DETAIL); + }); + + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] write chunk header", conn_id_, stream_id_); + return false; + } + + bool writeRequestChunk(SendDataEventPtr event) { + std::string str; + if (!event->getReqData().SerializeToString(&str)) { + ENVOY_LOG(error, "[TcpConn] [C{}][S{}] serialize request message failed", conn_id_, + stream_id_); + return false; + } + BufferPtr buffer = std::make_shared(); + uint32_t data_len = htonl(str.length()); + buffer->add(&data_len, sizeof(uint32_t)); + buffer->add(str); + + ASSERT_STREAM_LOG_RETURN_VAL(sd_->load(std::memory_order_relaxed), true); + dispatcher_->post( + [cbs = cbs_, buffer, sd = sd_, conn_id_ = conn_id_, stream_id_ = stream_id_] { + ASSERT_STREAM_LOG_RETURN(sd->load(std::memory_order_relaxed)); + cbs->encodeData(*buffer, false); + }); + + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] write chunk data", conn_id_, stream_id_); + return false; + } + + void close() { + // conn_->dispatcher().post( + // [conn = conn_] { conn->close(Network::ConnectionCloseType::FlushWriteAndDelay); }); + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] close tcp connection", conn_id_, stream_id_); + } + + bool delayed() { return delayed_; } + + bool writeResponse(RecvDataEventPtr event) { + auto& data = event->getRespData(); + auto hs = data.headers(); + bool end_stream = data.end_stream(); + bool is_chunked = data.chunk_data(); + int status_code = data.status_code(); + int index = data.index(); + ASSERT(end_stream || is_chunked); + + std::shared_ptr headers; + if (hs.size() > 0 || status_code != 0) { + headers = + std::make_shared(Http::ResponseHeaderMapImpl::create()); + (*headers)->setStatus(status_code); + for (auto iter = hs.begin(); iter != hs.end(); ++iter) { + if (!iter->first.empty() && !iter->second.empty()) { + (*headers)->setCopy(Envoy::Http::LowerCaseString(iter->first), iter->second); + } + } + } + + BufferPtr buffer; + if (data.body().size() > 0) { + buffer = std::make_shared(); + buffer->add(data.body()); + } + + ASSERT_STREAM_LOG_RETURN_VAL(sd_->load(std::memory_order_relaxed), true); + + dispatcher_->post([cbs = cbs_, headers = std::move(headers), buffer, end_stream, sd = sd_, + conn_id_ = conn_id_, stream_id_ = stream_id_] { + ASSERT_STREAM_LOG_RETURN(sd->load(std::memory_order_relaxed)); + if (headers) { + cbs->encodeHeaders(std::move(*headers), end_stream && !buffer, REPLY_DETAIL); + } + if (buffer) { + cbs->encodeData(*buffer, end_stream); + } + }); + + ENVOY_LOG( + trace, + "[TcpConn] [C{}][S{}] write response, status_code {} end_stream {} is_chunked {} index {}", + conn_id_, stream_id_, status_code, end_stream, is_chunked, index); + + return end_stream; + } + + void writeFailResponse(Http::Code code) { + // ASSERT_STREAM_LOG_RETURN_VAL(sd_->load(std::memory_order_relaxed), true); + dispatcher_->post([code = code, cbs = cbs_, sd = sd_, conn_id_ = conn_id_, + stream_id_ = stream_id_]() mutable { + ASSERT_STREAM_LOG_RETURN(sd->load(std::memory_order_relaxed)); + Http::ResponseHeaderMapPtr headers = Http::ResponseHeaderMapImpl::create(); + headers->setStatus(static_cast(code)); + cbs->encodeHeaders(std::move(headers), true, REPLY_DETAIL); + }); + + ENVOY_LOG(trace, "[TcpConn] [C{}][S{}] write fail response", conn_id_, stream_id_); + } + +protected: + Http::StreamDecoderFilterCallbacks* cbs_; + Event::Dispatcher* dispatcher_; + StreamDestroyPtr sd_; + uint64_t stream_id_; + uint64_t conn_id_; + bool delayed_; + +private: + static std::atomic active_conn_; +}; + +std::atomic TcpConn::active_conn_ = 0; + +using TcpConnPtr = std::shared_ptr; + +class BufferedConn : public TcpConn { +public: + explicit BufferedConn(Http::StreamDecoderFilterCallbacks* cbs, StreamDestroyPtr sd, + const std::string& uuid) + : TcpConn(cbs, sd), writeable_(true), uuid_(uuid) { + if (!delayed_) { + auto conn = const_cast(cbs_->connection().ptr()); + conn->setBufferLimits(buffer_limit_); + } + ENVOY_LOG(trace, "[BufferedConn] [C{}][S{}] constructor, active buffered conn count {}", + conn_id_, stream_id_, active_conn_.fetch_add(1, std::memory_order_relaxed) + 1); + } + + ~BufferedConn() { + ENVOY_LOG(trace, "[BufferedConn] [C{}][S{}] deconstructor, active buffered conn count {}", + conn_id_, stream_id_, active_conn_.fetch_add(-1, std::memory_order_relaxed) - 1); + } + + bool isWriteable() const { return writeable_; } + + void setWriteable(bool writable) { writeable_ = writable; } + + std::list takeEvents() { return std::move(events_); } + + bool empty() const { return events_.empty(); } + + size_t size() const { return events_.size(); } + + void addEvent(const SendDataEventPtr event) { events_.emplace_back(event); } + + std::string& getUuid() { return uuid_; } + +private: + std::list events_; + volatile bool writeable_; + std::string uuid_; + static std::atomic active_conn_; + constexpr static int buffer_limit_ = 1024 * 1024 * 100; +}; + +std::atomic BufferedConn::active_conn_ = 0; + +using BufferedConnPtr = std::shared_ptr; + +class EventGreater { +public: + bool operator()(const RecvDataEventPtr& lhs, const RecvDataEventPtr& rhs) const { + return lhs->getRespData().index() > rhs->getRespData().index(); + } +}; + +class IndexedConn : public TcpConn { +public: + explicit IndexedConn(Http::StreamDecoderFilterCallbacks* cbs, StreamDestroyPtr sd) + : TcpConn(cbs, sd), events_(EventGreater()), index_(0) { + ENVOY_LOG(trace, "[IndexedConn] [C{}][S{}] constructor, active indexed conn count {}", + conn_id_, stream_id_, active_conn_.fetch_add(1, std::memory_order_relaxed) + 1); + } + + ~IndexedConn() { + ENVOY_LOG(trace, "[IndexedConn] [C{}][S{}] deconstructor, active indexed conn count {}", + conn_id_, stream_id_, active_conn_.fetch_add(-1, std::memory_order_relaxed) - 1); + } + + bool writeResponseIndexed(RecvDataEventPtr event) { + auto& data = event->getRespData(); + if (data.index() == index_) { + if (writeResponse(event)) { + return true; + } + ++index_; + } else { + events_.push(event); + ENVOY_LOG(trace, "[IndexedConn] [C{}][S{}] push event to queue, index {}, queue size {}", + conn_id_, stream_id_, data.index(), events_.size()); + return false; + } + while (!events_.empty() && events_.top()->getRespData().index() == index_) { + auto event = events_.top(); + events_.pop(); + if (writeResponse(event)) { + return true; + } + ++index_; + } + return false; + } + +private: + std::priority_queue, EventGreater> events_; + static std::atomic active_conn_; + int index_; +}; + +std::atomic IndexedConn::active_conn_ = 0; + +using IndexedConnPtr = std::shared_ptr; + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_receiver/event.h b/kuscia/source/filters/http/kuscia_receiver/event.h new file mode 100644 index 0000000..4a3e7bb --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/event.h @@ -0,0 +1,213 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "envoy/http/header_map.h" +#include "include/nlohmann/json.hpp" +#include "kuscia/api/filters/http/kuscia_receiver/v3/message.pb.h" +#include "kuscia/api/filters/http/kuscia_receiver/v3/receiver.pb.h" +#include "source/common/buffer/buffer_impl.h" +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +using RequestMessagePb = envoy::extensions::filters::http::kuscia_receiver::v3::RequestMessage; +using ResponseMessagePb = envoy::extensions::filters::http::kuscia_receiver::v3::ResponseMessage; +using ReceiverPbConfig = envoy::extensions::filters::http::kuscia_receiver::v3::Receiver; +using ReceiverRule = envoy::extensions::filters::http::kuscia_receiver::v3::ReceiverRule; +using ReceiverRulePtr = std::shared_ptr; + +static constexpr absl::string_view REPLY_DETAIL = "poller_receiver"; + +enum ReceiverEventType { + RECEIVER_EVENT_TYPE_UNKNOWN = 0, + RECEIVER_EVENT_TYPE_CONNECT = 1, + RECEIVER_EVENT_TYPE_DATA_SEND = 2, + RECEIVER_EVENT_TYPE_DISCONNECT = 3, + RECEIVER_EVENT_TYPE_BUFFER_HIGH = 4, + RECEIVER_EVENT_TYPE_BUFFER_LOW = 5, + RECEIVER_EVENT_TYPE_DATA_RECV = 6, + RECEIVER_EVENT_TYPE_TIMEOUT = 7, + RECEIVER_EVENT_TYPE_CLOSE = 8 +}; + +class ReceiverEvent { +public: + ReceiverEvent() : event_type_(RECEIVER_EVENT_TYPE_UNKNOWN) {} + + ReceiverEventType getEventType() const { return event_type_; } + +protected: + ReceiverEventType event_type_; +}; + +using ReceiverEventPtr = std::shared_ptr; +using StreamDestroyPtr = std::shared_ptr>; + +class ConnectEvent : public ReceiverEvent { +public: + explicit ConnectEvent(Http::StreamDecoderFilterCallbacks* cbs, ReceiverRulePtr rule, + StreamDestroyPtr sd, const std::string& uuid) + : cbs_(cbs), sd_(sd), rule_(rule), uuid_(uuid) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_CONNECT; + } + + Http::StreamDecoderFilterCallbacks* getCbs() const { return cbs_; } + + ReceiverRulePtr getRule() const { return rule_; } + + StreamDestroyPtr getStreamDestroyed() const { return sd_; } + + std::string& getUuid() { return uuid_; } + +private: + Http::StreamDecoderFilterCallbacks* cbs_; + StreamDestroyPtr sd_; + ReceiverRulePtr rule_; + std::string uuid_; +}; + +using ConnectEventPtr = std::shared_ptr; + +class SendDataEvent : public ReceiverEvent { +public: + explicit SendDataEvent(Http::StreamDecoderFilterCallbacks* cbs, ReceiverRulePtr rule, + RequestMessagePb&& req, StreamDestroyPtr sd) + : cbs_(cbs), sd_(sd), req_(std::move(req)), rule_(rule) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_SEND; + } + + Http::StreamDecoderFilterCallbacks* getCbs() const { return cbs_; } + + ReceiverRulePtr getRule() const { return rule_; } + + RequestMessagePb& getReqData() { return req_; } + + StreamDestroyPtr getStreamDestroyed() const { return sd_; } + +private: + Http::StreamDecoderFilterCallbacks* cbs_; + StreamDestroyPtr sd_; + RequestMessagePb req_; + ReceiverRulePtr rule_; +}; + +using SendDataEventPtr = std::shared_ptr; + +class DisconnectEvent : public ReceiverEvent { +public: + explicit DisconnectEvent(ReceiverRulePtr rule, const std::string& uuid) + : rule_(rule), uuid_(uuid) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_DISCONNECT; + } + + ReceiverRulePtr getRule() const { return rule_; } + + std::string& getUuid() { return uuid_; } + +private: + ReceiverRulePtr rule_; + std::string uuid_; +}; + +using DisconnectEventPtr = std::shared_ptr; + +class BufferHighEvent : public ReceiverEvent { +public: + explicit BufferHighEvent(ReceiverRulePtr rule) : rule_(rule) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_BUFFER_HIGH; + } + + ReceiverRulePtr getRule() const { return rule_; } + +private: + ReceiverRulePtr rule_; +}; + +using BufferHighEventPtr = std::shared_ptr; + +class BufferLowEvent : public ReceiverEvent { +public: + explicit BufferLowEvent(ReceiverRulePtr rule) : rule_(rule) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_BUFFER_LOW; + } + + ReceiverRulePtr getRule() const { return rule_; } + +private: + ReceiverRulePtr rule_; +}; + +using BufferLowEventPtr = std::shared_ptr; + +class RecvDataEvent : public ReceiverEvent { +public: + explicit RecvDataEvent(ResponseMessagePb&& resp, std::string request_id) + : resp_(std::move(resp)), request_id_(std::move(request_id)) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_RECV; + } + + ResponseMessagePb& getRespData() { return resp_; } + + std::string& getRequestId() { return request_id_; } + + bool operator>(const RecvDataEvent& other) const { return resp_.index() > other.resp_.index(); } + +private: + ResponseMessagePb resp_; + std::string request_id_; +}; + +using RecvDataEventPtr = std::shared_ptr; + +class TimeoutEvent : public ReceiverEvent { +public: + explicit TimeoutEvent(ReceiverRulePtr rule, const std::string& uuid) : rule_(rule), uuid_(uuid) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_TIMEOUT; + } + + ReceiverRulePtr getRule() const { return rule_; } + + std::string& getUuid() { return uuid_; } + +private: + ReceiverRulePtr rule_; + std::string uuid_; +}; + +using TimeoutEventPtr = std::shared_ptr; + +class CloseEvent : public ReceiverEvent { +public: + explicit CloseEvent(std::string request_id) : request_id_(std::move(request_id)) { + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_CLOSE; + } + + std::string& getRequestId() { return request_id_; } + +private: + std::string request_id_; +}; + +using CloseEventPtr = std::shared_ptr; + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_receiver/event_loop.h b/kuscia/source/filters/http/kuscia_receiver/event_loop.h new file mode 100644 index 0000000..dd81762 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/event_loop.h @@ -0,0 +1,331 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "conn.h" +#include "envoy/http/async_client.h" +#include "envoy/http/codes.h" +#include "envoy/http/header_map.h" +#include "envoy/http/message.h" +#include "envoy/upstream/cluster_manager.h" +#include "event.h" +#include "source/common/buffer/buffer_impl.h" +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +static std::string rule2str(ReceiverRulePtr rule) { + return absl::StrCat(rule->source(), "/", rule->destination(), "/", rule->service()); +} + +static bool ruleMatch(ReceiverRulePtr x, ReceiverRulePtr y) { + return x->source() == y->source() && x->destination() == y->destination() && + x->service() == y->service(); +} + +constexpr static int queue_capacity = 100000; +constexpr static int miss_capacity = 2000; + +class EventLoopSingleton : Logger::Loggable { +public: + EventLoopSingleton(const EventLoopSingleton&) = delete; + EventLoopSingleton(const EventLoopSingleton&&) = delete; + EventLoopSingleton& operator=(const EventLoopSingleton&) = delete; + + static EventLoopSingleton& GetInstance() { + static EventLoopSingleton singleton(queue_capacity); + return singleton; + } + + void postEvent(ReceiverEventPtr event) { + bool full = false; + { + std::unique_lock lock(mutex_); + full_cv_.wait(lock, [this] { return events_.size() < capacity_ || stop_; }); + if (stop_) { + return; + } + events_.push_back(event); + if (events_.size() >= capacity_) { + full = true; + } + } + empty_cv_.notify_one(); + if (full) { + ENVOY_LOG(warn, "[EventLoopSingleton] event queue is full!"); + } + } + +private: + explicit EventLoopSingleton(int capacity) : capacity_(capacity), stop_(false) { + std::thread([this]() { run(); }).detach(); + } + + ~EventLoopSingleton() { stop(); } + + void run() { + while (!stop_) { + std::shared_ptr event; + { + std::unique_lock lock(mutex_); + empty_cv_.wait(lock, [this] { return !events_.empty() || stop_; }); + if (stop_) { + return; + } + event = std::move(events_.front()); + events_.pop_front(); + } + full_cv_.notify_one(); + dispatch(std::move(event)); + } + } + + void stop() { + stop_ = true; + empty_cv_.notify_one(); + full_cv_.notify_all(); + } + + void dispatch(ReceiverEventPtr event) { + switch (event->getEventType()) { + case ReceiverEventType::RECEIVER_EVENT_TYPE_CONNECT: + registerConn(std::static_pointer_cast(event)); + break; + case ReceiverEventType::RECEIVER_EVENT_TYPE_DISCONNECT: + unregisterConn(std::static_pointer_cast(event)); + break; + case ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_SEND: + sendData(std::static_pointer_cast(event)); + break; + case ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_RECV: + recvData(std::static_pointer_cast(event)); + break; + case ReceiverEventType::RECEIVER_EVENT_TYPE_BUFFER_HIGH: + stopConnWrite(std::static_pointer_cast(event)); + break; + case ReceiverEventType::RECEIVER_EVENT_TYPE_BUFFER_LOW: + resumeConnWrite(std::static_pointer_cast(event)); + break; + case ReceiverEventType::RECEIVER_EVENT_TYPE_TIMEOUT: + timeoutConn(std::static_pointer_cast(event)); + break; + case ReceiverEventType::RECEIVER_EVENT_TYPE_CLOSE: + closeConn(std::static_pointer_cast(event)); + break; + default: + break; + } + } + + void registerConn(ConnectEventPtr event) { + ENVOY_LOG(info, "[EventLoopSingleton] register connection {}", rule2str(event->getRule())); + auto rule = event->getRule(); + ASSERT(event->getCbs() != nullptr); + ASSERT(rule != nullptr); + + auto conn = std::make_shared(event->getCbs(), event->getStreamDestroyed(), + event->getUuid()); + if (conn->delayed()) { + ENVOY_LOG(warn, "[EventLoopSingleton] delayed poll connection {}", rule2str(rule)); + return; + } + auto key = rule2str(rule); + if (!conn->writeRequestHeader()) { + poll_conns_[key] = std::move(conn); + } + if (!miss_events_.empty()) { + ENVOY_LOG(warn, "[EventLoopSingleton] miss events size {}", miss_events_.size()); + requeueMissEvents(rule); + } + // curlRegister(rule, false); + } + + void requeueMissEvents(ReceiverRulePtr rule) { + for (auto iter = miss_events_.begin(); iter != miss_events_.end();) { + auto event = *iter; + if (event->getStreamDestroyed()->load(std::memory_order_relaxed)) { + miss_events_.erase(iter++); + continue; + } + if (ruleMatch(event->getRule(), rule)) { + ENVOY_LOG(warn, "[EventLoopSingleton] requeue miss event, key {} request_id {}", + rule2str(rule), event->getReqData().id()); + miss_events_.erase(iter++); + sendData(event); + continue; + } + ++iter; + } + } + + void unregisterConn(DisconnectEventPtr event) { + ENVOY_LOG(info, "[EventLoopSingleton] unregister connection {}", rule2str(event->getRule())); + auto rule = event->getRule(); + ASSERT(rule != nullptr); + + auto iter = poll_conns_.find(rule2str(rule)); + if (iter != poll_conns_.end()) { + if (event->getUuid() == iter->second->getUuid()) { + poll_conns_.erase(iter); + } + } + // curlUnregister(rule, false); + } + + void timeoutConn(TimeoutEventPtr event) { + ENVOY_LOG(info, "[EventLoopSingleton] timeout connection {}", rule2str(event->getRule())); + auto rule = event->getRule(); + ASSERT(rule != nullptr); + + auto key = rule2str(rule); + auto iter = poll_conns_.find(key); + if (iter != poll_conns_.end()) { + if (event->getUuid() == iter->second->getUuid()) { + iter->second->close(); + } + } + } + + void stopConnWrite(BufferHighEventPtr event) { + ENVOY_LOG(info, "[EventLoopSingleton] stop write connection {}", rule2str(event->getRule())); + auto rule = event->getRule(); + ASSERT(rule != nullptr); + + auto key = rule2str(rule); + auto iter = poll_conns_.find(key); + if (iter == poll_conns_.end()) { + return; + } + auto conn = iter->second; + conn->setWriteable(false); + } + + void resumeConnWrite(BufferLowEventPtr event) { + ENVOY_LOG(info, "[EventLoopSingleton] resume write connection {}", rule2str(event->getRule())); + auto rule = event->getRule(); + ASSERT(rule != nullptr); + + auto key = rule2str(rule); + auto iter = poll_conns_.find(key); + if (iter == poll_conns_.end()) { + return; + } + auto conn = iter->second; + conn->setWriteable(true); + if (!conn->empty()) { + events_.splice(events_.begin(), conn->takeEvents()); + } + } + + void sendData(SendDataEventPtr event) { + ENVOY_LOG(trace, "[EventLoopSingleton] send data to connection {}", + rule2str(event->getRule())); + auto rule = event->getRule(); + ASSERT(event->getCbs() != nullptr); + ASSERT(rule != nullptr); + + auto key = rule2str(rule); + auto iter = poll_conns_.find(key); + if (iter == poll_conns_.end()) { + ENVOY_LOG(warn, "[EventLoopSingleton] cannot find poll connection, key {}", key); + while (miss_events_.size() >= miss_capacity) { + replyMissEvents(std::move(miss_events_.front())); + miss_events_.pop_front(); + } + miss_events_.emplace_back(std::move(event)); + return; + } + + auto& data = event->getReqData(); + std::string request_id = data.id(); + auto recv_conn = std::make_shared(event->getCbs(), event->getStreamDestroyed()); + if (recv_conn->delayed()) { + ENVOY_LOG(warn, "[EventLoopSingleton] delayed recv conn, request_id {}", request_id); + return; + } + + // keep record + recv_conns_[request_id] = std::move(recv_conn); + ENVOY_LOG(trace, "[EventLoopSingleton] add recv conn pair {}", request_id); + + // send data through poll conn, store the event in the queue if conn not writable + auto poll_conn = iter->second; + if (!poll_conn->isWriteable()) { + poll_conn->addEvent(std::move(event)); + return; + } + poll_conn->writeRequestChunk(std::move(event)); + } + + void replyMissEvents(SendDataEventPtr event) { + auto request_id = event->getReqData().id(); + ENVOY_LOG(warn, "[EventLoopSingleton] reply miss events, key {} request_id {}", + rule2str(event->getRule()), request_id); + auto conn = std::make_shared(event->getCbs(), event->getStreamDestroyed()); + if (conn->delayed()) { + ENVOY_LOG(warn, "[EventLoopSingleton] delayed recv conn, request_id {}", request_id); + return; + } + conn->writeFailResponse(Http::Code::ServiceUnavailable); + } + + void recvData(RecvDataEventPtr event) { + ENVOY_LOG(trace, "[EventLoopSingleton] recv data from connection {}", event->getRequestId()); + auto request_id = event->getRequestId(); + ASSERT(!request_id.empty()); + + auto iter = recv_conns_.find(request_id); + if (iter == recv_conns_.end()) { + ENVOY_LOG(warn, "[EventLoopSingleton] cannot find recv connection, request_id {}", + request_id); + return; + } + ENVOY_LOG(trace, "[EventLoopSingleton] recv connection found, request_id {}", request_id); + iter->second->writeResponseIndexed(event); + } + + void closeConn(CloseEventPtr event) { + ENVOY_LOG(trace, "[EventLoopSingleton] close connection {}", event->getRequestId()); + recv_conns_.erase(event->getRequestId()); + } + +private: + std::unordered_map poll_conns_; + std::unordered_map recv_conns_; + std::list miss_events_; + std::list events_; + std::condition_variable empty_cv_; + std::condition_variable full_cv_; + std::mutex mutex_; + size_t capacity_; + bool stop_; +}; + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy diff --git a/kuscia/source/filters/http/kuscia_receiver/receiver_filter.cc b/kuscia/source/filters/http/kuscia_receiver/receiver_filter.cc new file mode 100644 index 0000000..e107ebf --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/receiver_filter.cc @@ -0,0 +1,284 @@ +// Copyright 2024 Ant Group Co., 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 "receiver_filter.h" +#include "event.h" +#include "event_loop.h" +#include "kuscia/source/filters/http/kuscia_common/kuscia_header.h" +#include "source/common/common/logger.h" +#include "source/common/common/random_generator.h" +#include "source/common/http/utility.h" +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +static int timeout2sec(std::string& timeout) { + static std::regex time_pattern("(?:(\\d+)h)?(?:(\\d+)m)?(?:(\\d+)s)?"); + int h = 0, m = 0, s = 0; + std::smatch match; + if (std::regex_match(timeout, match, time_pattern)) { + if (match[1].matched) { + h = std::stoi(match[1].str()); + } + if (match[2].matched) { + m = std::stoi(match[2].str()); + } + if (match[3].matched) { + s = std::stoi(match[3].str()); + } + } + return h * 3600 + m * 60 + s; +} + +static void setReqHeader(RequestMessagePb& pb, Http::RequestHeaderMap& headers) { + pb.set_host(std::string(headers.getHostValue())); + pb.set_path(std::string(headers.getPathValue())); + pb.set_method(std::string(headers.getMethodValue())); + auto hs = pb.mutable_headers(); + headers.forEach([&](absl::string_view key, absl::string_view value) -> bool { + (*hs)[std::string(key)] = std::string(value); + return true; + }); +} + +static void setReqBody(RequestMessagePb& pb, Buffer::OwnedImpl& body) { + pb.set_body(body.toString()); +} + +ReceiverFilter::ReceiverFilter(ReceiverFilterConfigSharedPtr config) + : config_(config), event_type_(ReceiverEventType::RECEIVER_EVENT_TYPE_UNKNOWN) { + sd_ = std::make_shared>(false); +} + +Http::FilterHeadersStatus ReceiverFilter::decodeHeaders(Http::RequestHeaderMap& headers, + bool end_stream) { + // chunked encoding is not supported + if (decoder_callbacks_->streamInfo().protocol() != Http::Protocol::Http11) { + return Http::FilterHeadersStatus::Continue; + } + if (isPollRequest(headers) || isForwardRequest(headers) || isForwardResponse(headers)) { + if (event_type_ == ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_SEND) { + setReqHeader(request_pb_, headers); + } + if (end_stream) { + postEvent(); + } + return Http::FilterHeadersStatus::StopIteration; + } + return Http::FilterHeadersStatus::Continue; +} + +Http::FilterDataStatus ReceiverFilter::decodeData(Buffer::Instance& data, bool end_stream) { + if (event_type_ != ReceiverEventType::RECEIVER_EVENT_TYPE_UNKNOWN) { + if (data.length() > 0) { + body_.add(data); + data.drain(data.length()); + } + if (end_stream) { + postEvent(); + } + return Http::FilterDataStatus::StopIterationNoBuffer; + } + return Http::FilterDataStatus::Continue; +} + +Http::FilterTrailersStatus ReceiverFilter::decodeTrailers(Http::RequestTrailerMap&) { + if (event_type_ != ReceiverEventType::RECEIVER_EVENT_TYPE_UNKNOWN) { + postEvent(); + return Http::FilterTrailersStatus::StopIteration; + } + return Http::FilterTrailersStatus::Continue; +} + +void ReceiverFilter::onDestroy() { + ReceiverEventPtr event; + switch (event_type_) { + case RECEIVER_EVENT_TYPE_CONNECT: + event = std::make_shared(rule_, conn_uuid_); + break; + case RECEIVER_EVENT_TYPE_DATA_SEND: + event = std::make_shared(request_id_); + break; + default: + return; + } + if (event != nullptr) { + // must be execute immediately + sd_->store(true, std::memory_order_relaxed); + // can be handle in async thread + EventLoopSingleton::GetInstance().postEvent(event); + } +} + +// poll request check +// receiver.${peer}.svc/poll?timeout=xxx&service=xxx +bool ReceiverFilter::isPollRequest(Http::RequestHeaderMap& headers) { + auto path = headers.getPathValue(); + auto host = headers.getHostValue(); + auto source = headers.getByKey(KusciaCommon::HeaderKeyOriginSource).value_or(nullptr); + if (source == nullptr || host == nullptr || path == nullptr) { + return false; + } + std::string group; + if (!re2::RE2::PartialMatch(std::string(host), KusciaCommon::PollHostPattern, &group) || + !absl::StartsWith(path, KusciaCommon::PollPathPrefix) || group != config_->selfNamespace()) { + return false; + } + auto query_params = Http::Utility::parseQueryString(path); + auto svc = query_params.find(KusciaCommon::ServiceParamKey); + if (svc == query_params.end() && svc->second.empty()) { + return false; + } + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_CONNECT; + rule_ = std::make_shared(); + rule_->set_source(group); + rule_->set_destination(std::string(source)); + rule_->set_service(std::string(svc->second)); + conn_uuid_ = random_.uuid(); + + auto timeout = query_params.find(KusciaCommon::TimeoutParamKey); + if (timeout != query_params.end() && !timeout->second.empty()) { + timeout_sec_ = timeout2sec(timeout->second); + ENVOY_LOG(info, "[ReceiverFilter] poll request from {} to {} service {} timeout {}", group, + source, svc->second, timeout->second); + } + return true; +} + +// dst = ${svc}.dest-namespace.svc +// src = src-namespace +bool ReceiverFilter::isForwardRequest(Http::RequestHeaderMap& headers) { + auto source = headers.getByKey(KusciaCommon::HeaderKeyOriginSource).value_or(nullptr); + auto host = headers.getHostValue(); + // rewrite + bool rewrite = false; + if (absl::StartsWith(host, KusciaCommon::InternalClusterHost)) { + host = headers.getByKey(KusciaCommon::HeaderKeyKusciaHost).value_or(nullptr); + rewrite = true; + } + if (source != config_->selfNamespace() || host == nullptr) { + return false; + } + std::vector fields = absl::StrSplit(host, "."); + if (fields.size() != 3 || fields[0].empty() || fields[1].empty() || fields[2] != "svc") { + return false; + } + if (!config_->hasRule(source, fields[1])) { + return false; + } + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_SEND; + rule_ = std::make_shared(); + rule_->set_source(std::string(source)); + rule_->set_destination(std::string(fields[1])); + rule_->set_service(std::string(fields[0])); + request_id_ = random_.uuid(); + if (rewrite) { + headers.setHost(host); + } + return true; +} + +// receiver.xx.svc/reply?msgid=xxx +bool ReceiverFilter::isForwardResponse(Http::RequestHeaderMap& headers) { + auto path = headers.getPathValue(); + auto host = headers.getHostValue(); + if (!absl::StartsWith(path, KusciaCommon::ReplyPathPrefix) || host == nullptr) { + return false; + } + std::vector fields = absl::StrSplit(host, "."); + if (fields.size() != 3 || fields[0] != "receiver" || fields[1].empty() || fields[2] != "svc") { + return false; + } + if (fields[1] != config_->selfNamespace()) { + return false; + } + auto query_params = Http::Utility::parseQueryString(path); + auto request_id = query_params.find(KusciaCommon::RequestIdParamKey); + if (request_id == query_params.end() || request_id->second.empty()) { + return false; + } + event_type_ = ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_RECV; + request_id_ = request_id->second; + ENVOY_LOG(trace, "[ReceiverFilter] forward response request_id {}", request_id_); + return true; +} + +void ReceiverFilter::postEvent() { + ReceiverEventPtr event; + switch (event_type_) { + case ReceiverEventType::RECEIVER_EVENT_TYPE_CONNECT: { + ASSERT(decoder_callbacks_->connection().has_value()); + event = std::make_shared(decoder_callbacks_, rule_, sd_, conn_uuid_); + if (timeout_sec_ > 0) { + ENVOY_LOG(trace, "[ReceiverFilter] connect timeout {}", timeout_sec_); + registerConnTimeout(); + } + break; + } + case ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_RECV: { + ResponseMessagePb pb; + if (!pb.ParseFromString(body_.toString())) { + ENVOY_LOG(warn, "[ReceiverFilter] parse response message failed!"); + replyDirectly(Http::Code::BadRequest); + return; + } + ENVOY_LOG(trace, "[ReceiverFilter] get request_id {}", request_id_); + event = std::make_shared(std::move(pb), request_id_); + replyDirectly(Http::Code::OK); + break; + } + case ReceiverEventType::RECEIVER_EVENT_TYPE_DATA_SEND: { + setReqBody(request_pb_, body_); + request_pb_.set_id(request_id_); + event = + std::make_shared(decoder_callbacks_, rule_, std::move(request_pb_), sd_); + break; + } + default: + return; + } + EventLoopSingleton::GetInstance().postEvent(std::move(event)); +} + +void ReceiverFilter::replyDirectly(Http::Code code) { + decoder_callbacks_->sendLocalReply( + code, // HTTP status code + "", // Response body text + [](Http::ResponseHeaderMap& response_headers) { + // Modify response headers if needed + response_headers.setReferenceKey(Envoy::Http::LowerCaseString("content-type"), + "text/plain"); + }, + absl::nullopt, // grpc_status (not needed for non-gRPC responses) + REPLY_DETAIL); // A flag that indicates why this local reply was sent +} + +// May remove later, not necessary +void ReceiverFilter::registerConnTimeout() { + ReceiverEventPtr event = std::make_shared(rule_, conn_uuid_); + Event::TimerPtr timer = + decoder_callbacks_->dispatcher().createTimer([event{std::move(event)}]() mutable { + EventLoopSingleton::GetInstance().postEvent(std::move(event)); + }); + std::chrono::milliseconds timeout(timeout_sec_ * 1000); + timer->enableTimer(timeout); +} + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_receiver/receiver_filter.h b/kuscia/source/filters/http/kuscia_receiver/receiver_filter.h new file mode 100644 index 0000000..2b104d4 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/receiver_filter.h @@ -0,0 +1,99 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "envoy/http/filter.h" +#include "envoy/network/connection.h" +#include "event.h" +#include "kuscia/api/filters/http/kuscia_receiver/v3/message.pb.h" +#include "source/common/common/logger.h" +#include "source/common/common/random_generator.h" +#include "source/common/http/utility.h" +#include "source/extensions/filters/http/common/factory_base.h" +#include "source/extensions/filters/http/common/pass_through_filter.h" +#include +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +static std::string genKey(absl::string_view src, absl::string_view dst) { + return absl::StrCat(src, "/", dst); +} + +class ReceiverFilterConfig { +public: + explicit ReceiverFilterConfig(const ReceiverPbConfig& config, + Server::Configuration::FactoryContext& context) + : context_(context) { + namespace_ = config.self_namespace(); + for (auto& rule : config.rules()) { + auto key = genKey(rule.source(), rule.destination()); + rules_.emplace(std::move(key), true); + } + } + + bool hasRule(absl::string_view source, absl::string_view dest) const { + return rules_.find(genKey(source, dest)) != rules_.end(); + } + + absl::string_view selfNamespace() const { return namespace_; } + +private: + Server::Configuration::FactoryContext& context_; + std::map rules_; + std::string namespace_; +}; + +using ReceiverFilterConfigSharedPtr = std::shared_ptr; + +class ReceiverFilter : public Http::PassThroughFilter, Logger::Loggable { +public: + explicit ReceiverFilter(ReceiverFilterConfigSharedPtr config); + + Http::FilterHeadersStatus decodeHeaders(Http::RequestHeaderMap& headers, + bool end_stream) override; + Http::FilterDataStatus decodeData(Buffer::Instance& data, bool end_stream) override; + Http::FilterTrailersStatus decodeTrailers(Http::RequestTrailerMap&) override; + void onDestroy() override; + +private: + bool isPollRequest(Http::RequestHeaderMap& headers); + bool isForwardRequest(Http::RequestHeaderMap& headers); + bool isForwardResponse(Http::RequestHeaderMap& headers); + + void postEvent(); + void replyDirectly(Http::Code code); + void registerConnTimeout(); + +private: + const ReceiverFilterConfigSharedPtr config_; + Random::RandomGeneratorImpl random_; + RequestMessagePb request_pb_; + std::string request_id_; + std::string conn_uuid_; + ReceiverEventType event_type_; + Buffer::OwnedImpl body_; + ReceiverRulePtr rule_; + StreamDestroyPtr sd_; + int timeout_sec_{0}; +}; + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy \ No newline at end of file diff --git a/kuscia/source/filters/http/kuscia_receiver/svc_register.h b/kuscia/source/filters/http/kuscia_receiver/svc_register.h new file mode 100644 index 0000000..50cc836 --- /dev/null +++ b/kuscia/source/filters/http/kuscia_receiver/svc_register.h @@ -0,0 +1,205 @@ +// Copyright 2024 Ant Group Co., 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. + +#pragma once + +#include "envoy/http/header_map.h" +#include "envoy/runtime/runtime.h" +#include "envoy/upstream/cluster_manager.h" +#include "kuscia/source/filters/http/kuscia_common/kuscia_header.h" +#include "source/common/api/os_sys_calls_impl.h" +#include "source/common/http/headers.h" +#include "source/common/http/message_impl.h" +#include + +namespace Envoy { +namespace Extensions { +namespace HttpFilters { +namespace KusciaReceiver { + +class SVCRegister : public Http::AsyncClient::Callbacks { +public: + explicit SVCRegister(Upstream::ClusterManager& cm) : cm_(cm) {} + + void registerSVC(Http::RequestMessagePtr message) { + const auto cluster = cm_.getThreadLocalCluster(KusciaCommon::GatewayClusterName); + if (cluster != nullptr) { + cluster->httpAsyncClient().send( + std::move(message), *this, + Http::AsyncClient::RequestOptions().setTimeout(std::chrono::milliseconds(1000))); + } + } + + void unregisterSVC(Http::RequestMessagePtr message) { + const auto cluster = cm_.getThreadLocalCluster(KusciaCommon::GatewayClusterName); + if (cluster != nullptr) { + cluster->httpAsyncClient().send( + std::move(message), *this, + Http::AsyncClient::RequestOptions().setTimeout(std::chrono::milliseconds(1000))); + } + } + +private: + void onSuccess(const Http::AsyncClient::Request&, Http::ResponseMessagePtr&&) override {} + void onFailure(const Http::AsyncClient::Request&, Http::AsyncClient::FailureReason) override {} + void onBeforeFinalizeUpstreamSpan(Envoy::Tracing::Span&, + const Http::ResponseHeaderMap*) override {} + +private: + Upstream::ClusterManager& cm_; +}; + +static std::string getLocalhost() { + std::string local_ip; + if (!Api::OsSysCallsSingleton::get().supportsGetifaddrs()) { + return local_ip; + } + // iteration over ifaddrs + Api::InterfaceAddressVector interface_addresses{}; + const Api::SysCallIntResult rc = Api::OsSysCallsSingleton::get().getifaddrs(interface_addresses); + if (rc.return_value_ == -1) { + return local_ip; + } + for (const auto& addr : interface_addresses) { + if (addr.interface_name_ == "eth0") { + return addr.interface_addr_->ip()->addressAsString(); + } + } + return local_ip; +} + +static size_t curlCallback(char* ptr, size_t, size_t nmemb, void* data) { + auto buf = static_cast(data); + buf->append(ptr, nmemb); + return nmemb; +} + +static absl::optional doCurl(Http::RequestMessagePtr message) { + static const size_t MAX_RETRIES = 2; + static const std::chrono::milliseconds RETRY_DELAY{1000}; + static const std::chrono::seconds TIMEOUT{5}; + + CURL* const curl = curl_easy_init(); + if (!curl) { + return absl::nullopt; + }; + + const auto host = message->headers().getHostValue(); + const auto path = message->headers().getPathValue(); + const auto method = message->headers().getMethodValue(); + + const std::string url = fmt::format("http://{}{}", host, path); + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_TIMEOUT, TIMEOUT.count()); + curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L); + curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); + + std::string buffer(message->body().toString()); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &buffer); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curlCallback); + + struct curl_slist* headers = nullptr; + message->headers().iterate( + [&headers](const Http::HeaderEntry& entry) -> Http::HeaderMap::Iterate { + // Skip pseudo-headers + if (!entry.key().getStringView().empty() && entry.key().getStringView()[0] == ':') { + return Http::HeaderMap::Iterate::Continue; + } + const std::string header = + fmt::format("{}: {}", entry.key().getStringView(), entry.value().getStringView()); + headers = curl_slist_append(headers, header.c_str()); + return Http::HeaderMap::Iterate::Continue; + }); + + if (Http::Headers::get().MethodValues.Put == method) { + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + curl_easy_setopt(curl, CURLOPT_INFILESIZE, 0); + headers = curl_slist_append(headers, "Expect:"); + } + + if (headers != nullptr) { + curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); + } + + for (size_t retry = 0; retry < MAX_RETRIES; retry++) { + const CURLcode res = curl_easy_perform(curl); + if (res == CURLE_OK) { + break; + } + buffer.clear(); + std::this_thread::sleep_for(RETRY_DELAY); + } + + curl_easy_cleanup(curl); + curl_slist_free_all(headers); + + return buffer.empty() ? absl::nullopt : absl::optional(buffer); +} + +Http::RequestMessagePtr createRegisterRequest(ReceiverRulePtr rule) { + Http::RequestMessagePtr message(new Http::RequestMessageImpl()); + message->headers().setMethod(Http::Headers::get().MethodValues.Post); + message->headers().setPath(KusciaCommon::GatewayRegisterPath); + message->headers().setHost(KusciaCommon::GatewayHostName); + message->headers().setContentType(Http::Headers::get().ContentTypeValues.Json); + nlohmann::json json = {"src", rule->source(), "dst", rule->destination(), + "svc", rule->service(), "host", getLocalhost()}; + message->body().add(json.dump()); + return message; +} + +Http::RequestMessagePtr createUnregisterRequest(ReceiverRulePtr rule) { + Http::RequestMessagePtr message(new Http::RequestMessageImpl()); + message->headers().setMethod(Http::Headers::get().MethodValues.Post); + message->headers().setPath(KusciaCommon::GatewayUnregisterPath); + message->headers().setHost(KusciaCommon::GatewayHostName); + message->headers().setContentType(Http::Headers::get().ContentTypeValues.Json); + nlohmann::json json = {"src", rule->source(), "dst", rule->destination(), + "svc", rule->service(), "host", getLocalhost()}; + message->body().add(json.dump()); + return message; +} + +void curlRegister(ReceiverRulePtr rule, bool block) { + Http::RequestMessagePtr message = createRegisterRequest(rule); + if (!block) { + std::thread([message{std::move(message)}]() mutable { doCurl(std::move(message)); }).detach(); + } else { + doCurl(std::move(message)); + } +} + +void curlUnregister(ReceiverRulePtr rule, bool block) { + Http::RequestMessagePtr message = createUnregisterRequest(rule); + if (!block) { + std::thread([message{std::move(message)}]() mutable { doCurl(std::move(message)); }).detach(); + } else { + doCurl(std::move(message)); + } +} + +void nativeRegister(ReceiverRulePtr rule, Upstream::ClusterManager& cm) { + SVCRegister svc_register(cm); + svc_register.registerSVC(createRegisterRequest(rule)); +} + +void nativeUnregister(ReceiverRulePtr rule, Upstream::ClusterManager& cm) { + SVCRegister svc_register(cm); + svc_register.unregisterSVC(createUnregisterRequest(rule)); +} + +} // namespace KusciaReceiver +} // namespace HttpFilters +} // namespace Extensions +} // namespace Envoy