Repository: apache/dubbo-go-pixiu

Issue:
[Proposal] Use a more modern entry controller application gateway design
### ✅ 验证清单

- [x] 🔍 我已经搜索过 [现有 Issues](https://github.com/apache/dubbo-go-pixiu/issues)，确信这不是重复问题

### ✨ 提案摘要

In the previous controller design, we used both Ingress and Gateway API resource objects and watched for their changes, supporting interfaces such as proxy.

In the latest design, the Ingress resource objects and proxy interfaces have been removed. Only Gateway API resource objects are supported. The reason is that Ingress resources have limited functionality and make future extensibility difficult.

### 🛠️ 实现方法

Therefore, a translator and a converter have been added.
The translator can convert standard Gateway API resources into an implementation-agnostic IR xDS.
Using IR xDS allows unified management of all types of listeners. However, this xDS can be considered static xDS, because in Pixiu the conf.yaml belongs to static_resources, meaning it is not a dynamic configuration protocol.

The converter also uses ConfigMap, so if dynamic resources were required, the ConfigMap would need to be replaced with an actual xDS server.
The converter transforms the standardized IR xDS into a configuration format that Pixiu can understand and execute, which is the content of conf.yaml.

<img width="362" height="474" alt="Image" src="https://github.com/user-attachments/assets/1edd890a-89c1-4e12-a1da-50ea66af20bd" />

When we create a Gateway, the built-in Pixiu Gateway resource automatically creates two objects:
a Pixiu Gateway Deployment and a LoadBalancer Service.
After these are created, the process continues through the conf.yaml ConfigMap, and finally the Pixiu Gateway is fully created.

<img width="256" height="642" alt="Image" src="https://github.com/user-attachments/assets/e8a13ee3-e22d-4f28-b7e0-13a226b47f3f" />

### 📚 附加上下文

_No response_

Hints from issue comments:
@mfordjody: Done.

Solution patch:
diff --git a/.licenserc.yaml b/.licenserc.yaml
index d6f83cf8..851902e3 100644
--- a/.licenserc.yaml
+++ b/.licenserc.yaml
@@ -42,6 +42,7 @@ header:
     - 'LICENSE'
     - 'licenses'
     - 'NOTICE'
+    - '**/bin/**'
 
     # ignore files clone from istio
     - 'istio/pkg/test/eventually.go'
diff --git a/controllers/Makefile b/controllers/Makefile
index 234fc2fb..3e19f6c4 100644
--- a/controllers/Makefile
+++ b/controllers/Makefile
@@ -79,14 +79,14 @@ vet: ## Run go vet against code.
 	go vet ./...
 
 .PHONY: test
-test: manifests generate fmt vet setup-envtest ## Run tests.
+test: fmt vet setup-envtest ## Run tests.
 	KUBEBUILDER_ASSETS="$(shell $(ENVTEST) use $(ENVTEST_K8S_VERSION) --bin-dir $(LOCALBIN) -p path)" go test $$(go list ./... | grep -v /e2e) -coverprofile cover.out
 
 # TODO(user): To use a different vendor for e2e tests, modify the setup under 'tests/e2e'.
 # The default setup assumes Kind is pre-installed and builds/loads the Manager Docker image locally.
 # CertManager is installed by default; skip with:
 # - CERT_MANAGER_INSTALL_SKIP=true
-KIND_CLUSTER ?= -ingress-controller-test-e2e
+KIND_CLUSTER ?= -gateway-controller-test-e2e
 
 .PHONY: setup-test-e2e
 setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
@@ -103,7 +103,7 @@ setup-test-e2e: ## Set up a Kind cluster for e2e tests if it does not exist
 	esac
 
 .PHONY: test-e2e
-test-e2e: setup-test-e2e manifests generate fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
+test-e2e: setup-test-e2e fmt vet ## Run the e2e tests. Expected an isolated environment using Kind.
 	KIND_CLUSTER=$(KIND_CLUSTER) go test ./test/e2e/ -v -ginkgo.v
 	$(MAKE) cleanup-test-e2e
 
@@ -126,12 +126,12 @@ lint-config: golangci-lint ## Verify golangci-lint linter configuration
 ##@ Build
 
 .PHONY: build
-build: manifests generate fmt vet ## Build manager binary.
-	go build -o bin/manager /cmd/cli/main.go
+build: fmt vet ## Build manager binary.
+	go build -o bin/pgctl ./cmd/pgctl/main.go
 
 .PHONY: run
-run: manifests generate fmt vet ## Run a controller from your host.
-	go run ./cmd/cli/main.go
+run: fmt vet ## Run a controller from your host.
+	go run ./cmd/pgctl/main.go
 
 # If you wish to build the manager image targeting other platforms you can use the --platform flag.
 # (i.e. docker build --platform linux/arm64). However, you must enable docker buildKit for it.
@@ -155,14 +155,14 @@ PLATFORMS ?= linux/arm64,linux/amd64,linux/s390x,linux/ppc64le
 docker-buildx: ## Build and push docker image for the manager for cross-platform support
 	# copy existing Dockerfile and insert --platform=${BUILDPLATFORM} into Dockerfile.cross, and preserve the original Dockerfile
 	sed -e '1 s/\(^FROM\)/FROM --platform=\$$\{BUILDPLATFORM\}/; t' -e ' 1,// s//FROM --platform=\$$\{BUILDPLATFORM\}/' Dockerfile > Dockerfile.cross
-	- $(CONTAINER_TOOL) buildx create --name pixiu-ingress-controller-builder
-	$(CONTAINER_TOOL) buildx use pixiu-ingress-controller-builder
+	- $(CONTAINER_TOOL) buildx create --name pixiu-gateway-controller-builder
+	$(CONTAINER_TOOL) buildx use pixiu-gateway-controller-builder
 	- $(CONTAINER_TOOL) buildx build --push --platform=$(PLATFORMS) --tag ${IMG} -f Dockerfile.cross .
-	- $(CONTAINER_TOOL) buildx rm pixiu-ingress-controller-builder
+	- $(CONTAINER_TOOL) buildx rm pixiu-gateway-controller-builder
 	rm Dockerfile.cross
 
 .PHONY: build-installer
-build-installer: manifests generate kustomize ## Generate a consolidated YAML with CRDs and deployment.
+build-installer: kustomize ## Generate a consolidated YAML with CRDs and deployment.
 	mkdir -p dist
 	cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
 	$(KUSTOMIZE) build config/default > dist/install.yaml
@@ -174,11 +174,11 @@ ifndef ignore-not-found
 endif
 
 .PHONY: install
-install: manifests kustomize install-gateway-api ## Install CRDs into the K8s cluster specified in ~/.kube/config.
+install: kustomize install-gateway-api ## Install CRDs into the K8s cluster specified in ~/.kube/config.
 	$(KUSTOMIZE) build config/crd | $(KUBECTL) apply -f -
 
 .PHONY: uninstall
-uninstall: manifests kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
+uninstall: kustomize ## Uninstall CRDs from the K8s cluster specified in ~/.kube/config. Call with ignore-not-found=true to ignore resource not found errors during deletion.
 	$(KUSTOMIZE) build config/crd | $(KUBECTL) delete --ignore-not-found=$(ignore-not-found) -f -
 
 .PHONY: install-gateway-api
@@ -190,7 +190,7 @@ uninstall-gateway-api: ## Uninstall Gateway API CRDs from the K8s cluster specif
 	kubectl delete -f https://github.com/kubernetes-sigs/gateway-api/releases/download/$(GATEAY_API_VERSION)/standard-install.yaml
 
 .PHONY: deploy
-deploy: manifests kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
+deploy: kustomize ## Deploy controller to the K8s cluster specified in ~/.kube/config.
 	cd config/manager && $(KUSTOMIZE) edit set image controller=${IMG}
 	$(KUSTOMIZE) build config/default | $(KUBECTL) apply -f -
 
diff --git a/controllers/README.md b/controllers/README.md
deleted file mode 100644
index d7ca2143..00000000
--- a/controllers/README.md
+++ /dev/null
@@ -1,135 +0,0 @@
-# controllers
-// TODO(user): Add simple overview of use/purpose
-
-## Description
-// TODO(user): An in-depth paragraph about your project and overview of use
-
-## Getting Started
-
-### Prerequisites
-- go version v1.24.0+
-- docker version 17.03+.
-- kubectl version v1.11.3+.
-- Access to a Kubernetes v1.11.3+ cluster.
-
-### To Deploy on the cluster
-**Build and push your image to the location specified by `IMG`:**
-
-```sh
-make docker-build docker-push IMG=<some-registry>/controllers:tag
-```
-
-**NOTE:** This image ought to be published in the personal registry you specified.
-And it is required to have access to pull the image from the working environment.
-Make sure you have the proper permission to the registry if the above commands don’t work.
-
-**Install the CRDs into the cluster:**
-
-```sh
-make install
-```
-
-**Deploy the Manager to the cluster with the image specified by `IMG`:**
-
-```sh
-make deploy IMG=<some-registry>/controllers:tag
-```
-
-> **NOTE**: If you encounter RBAC errors, you may need to grant yourself cluster-admin
-privileges or be logged in as admin.
-
-**Create instances of your solution**
-You can apply the samples (examples) from the config/sample:
-
-```sh
-kubectl apply -k config/samples/
-```
-
->**NOTE**: Ensure that the samples has default values to test it out.
-
-### To Uninstall
-**Delete the instances (CRs) from the cluster:**
-
-```sh
-kubectl delete -k config/samples/
-```
-
-**Delete the APIs(CRDs) from the cluster:**
-
-```sh
-make uninstall
-```
-
-**UnDeploy the controller from the cluster:**
-
-```sh
-make undeploy
-```
-
-## Project Distribution
-
-Following the options to release and provide this solution to the users.
-
-### By providing a bundle with all YAML files
-
-1. Build the installer for the image built and published in the registry:
-
-```sh
-make build-installer IMG=<some-registry>/controllers:tag
-```
-
-**NOTE:** The makefile target mentioned above generates an 'install.yaml'
-file in the dist directory. This file contains all the resources built
-with Kustomize, which are necessary to install this project without its
-dependencies.
-
-2. Using the installer
-
-Users can just run 'kubectl apply -f <URL for YAML BUNDLE>' to install
-the project, i.e.:
-
-```sh
-kubectl apply -f https://raw.githubusercontent.com/<org>/controllers/<tag or branch>/dist/install.yaml
-```
-
-### By providing a Helm Chart
-
-1. Build the chart using the optional helm plugin
-
-```sh
-kubebuilder edit --plugins=helm/v1-alpha
-```
-
-2. See that a chart was generated under 'dist/chart', and users
-can obtain this solution from there.
-
-**NOTE:** If you change the project, you need to update the Helm Chart
-using the same command above to sync the latest changes. Furthermore,
-if you create webhooks, you need to use the above command with
-the '--force' flag and manually ensure that any custom configuration
-previously added to 'dist/chart/values.yaml' or 'dist/chart/manager/manager.yaml'
-is manually re-applied afterwards.
-
-## Contributing
-// TODO(user): Add detailed information on how you would like others to contribute to this project
-
-**NOTE:** Run `make help` for more information on all potential `make` targets
-
-More information can be found via the [Kubebuilder Documentation](https://book.kubebuilder.io/introduction.html)
-
-## License
-
-Copyright 2025 dubbo.apache.org.
-
-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.
-
diff --git a/controllers/api/v1alpha1/gatewayproxy_types.go b/controllers/api/v1alpha1/gatewayproxy_types.go
deleted file mode 100644
index a305c312..00000000
--- a/controllers/api/v1alpha1/gatewayproxy_types.go
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package v1alpha1
-
-import (
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-// EDIT THIS FILE!  THIS IS SCAFFOLDING FOR YOU TO OWN!
-// NOTE: json tags are required.  Any new fields you add must have json tags for the fields to be serialized.
-// GatewayProxySpec defines the desired state of GatewayProxy
-type GatewayProxySpec struct{}
-
-// GatewayProxyStatus defines the observed state of GatewayProxy.
-
-type GatewayProxyStatus struct {
-	// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
-	// Important: Run "make" to regenerate code after modifying this file
-}
-
-// +kubebuilder:object:root=true
-// +kubebuilder:subresource:status
-
-// GatewayProxy is the Schema for the gatewayproxies API
-type GatewayProxy struct {
-	metav1.TypeMeta   `json:",inline"`
-	metav1.ObjectMeta `json:"metadata,omitempty"`
-
-	// GatewayProxySpec defines configuration of gateway proxy instances,
-	// including networking settings, global plugins, and plugin metadata.
-	Spec GatewayProxySpec `json:"spec,omitempty"`
-}
-
-// +kubebuilder:object:root=true
-
-// GatewayProxyList contains a list of GatewayProxy
-type GatewayProxyList struct {
-	metav1.TypeMeta `json:",inline"`
-	metav1.ListMeta `json:"metadata,omitempty"`
-	Items           []GatewayProxy `json:"items"`
-}
-
-func init() {
-	SchemeBuilder.Register(&GatewayProxy{}, &GatewayProxyList{})
-}
diff --git a/controllers/api/v1alpha1/zz_generated.deepcopy.go b/controllers/api/v1alpha1/zz_generated.deepcopy.go
index 62726fa7..8924ce82 100644
--- a/controllers/api/v1alpha1/zz_generated.deepcopy.go
+++ b/controllers/api/v1alpha1/zz_generated.deepcopy.go
@@ -1,112 +1,22 @@
 //go:build !ignore_autogenerated
-// +build !ignore_autogenerated
 
 /*
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You 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.
-*/
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 controller-gen. DO NOT EDIT.
-package v1alpha1
-
-import (
-	runtime "k8s.io/apimachinery/pkg/runtime"
-)
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GatewayProxy) DeepCopyInto(out *GatewayProxy) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
-	out.Spec = in.Spec
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayProxy.
-func (in *GatewayProxy) DeepCopy() *GatewayProxy {
-	if in == nil {
-		return nil
-	}
-	out := new(GatewayProxy)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GatewayProxy) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GatewayProxyList) DeepCopyInto(out *GatewayProxyList) {
-	*out = *in
-	out.TypeMeta = in.TypeMeta
-	in.ListMeta.DeepCopyInto(&out.ListMeta)
-	if in.Items != nil {
-		in, out := &in.Items, &out.Items
-		*out = make([]GatewayProxy, len(*in))
-		for i := range *in {
-			(*in)[i].DeepCopyInto(&(*out)[i])
-		}
-	}
-}
 
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayProxyList.
-func (in *GatewayProxyList) DeepCopy() *GatewayProxyList {
-	if in == nil {
-		return nil
-	}
-	out := new(GatewayProxyList)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
-func (in *GatewayProxyList) DeepCopyObject() runtime.Object {
-	if c := in.DeepCopy(); c != nil {
-		return c
-	}
-	return nil
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GatewayProxySpec) DeepCopyInto(out *GatewayProxySpec) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayProxySpec.
-func (in *GatewayProxySpec) DeepCopy() *GatewayProxySpec {
-	if in == nil {
-		return nil
-	}
-	out := new(GatewayProxySpec)
-	in.DeepCopyInto(out)
-	return out
-}
-
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *GatewayProxyStatus) DeepCopyInto(out *GatewayProxyStatus) {
-	*out = *in
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GatewayProxyStatus.
-func (in *GatewayProxyStatus) DeepCopy() *GatewayProxyStatus {
-	if in == nil {
-		return nil
-	}
-	out := new(GatewayProxyStatus)
-	in.DeepCopyInto(out)
-	return out
-}
+package v1alpha1
diff --git a/controllers/cmd/cli/main.go b/controllers/cmd/pgctl/main.go
similarity index 100%
rename from controllers/cmd/cli/main.go
rename to controllers/cmd/pgctl/main.go
diff --git a/controllers/cmd/root.go b/controllers/cmd/root.go
index d1c47c46..d62a1d3d 100644
--- a/controllers/cmd/root.go
+++ b/controllers/cmd/root.go
@@ -37,25 +37,30 @@ import (
 )
 
 func GetRootCmd() *cobra.Command {
-	root := newPixiuIngressController()
+	root := newPixiuGatewayController()
 	return root
 }
 
-func newPixiuIngressController() *cobra.Command {
-	cfg := config.ControllerConfig
+func newPixiuGatewayController() *cobra.Command {
 	var configPath string
 	cmd := &cobra.Command{
-		Use:  "pixiu-ingress-controller [command]",
+		Use:  "pixiu-gateway-controller [command]",
 		Long: "",
 		RunE: func(cmd *cobra.Command, args []string) error {
+			cfg := config.ControllerConfig
 			if configPath != "" {
 				c, err := config.GetConfigFromFile(configPath)
 				if err != nil {
-					return err
+					// If config file doesn't exist, log warning and continue with default config
+					// This allows the controller to start even if config file is missing
+					cmd.Printf("Warning: failed to load config from %s: %v, using default config\n", configPath, err)
+				} else {
+					cfg = c
+					config.SetControllerConfig(c)
 				}
-				cfg = c
-				config.SetControllerConfig(c)
 			}
+			// Command line flags are already bound to config.ControllerConfig,
+			// so cfg (which is config.ControllerConfig) already has the updated values
 
 			logLevel, err := zapcore.ParseLevel(cfg.LogLevel)
 			if err != nil {
@@ -78,12 +83,12 @@ func newPixiuIngressController() *cobra.Command {
 		},
 	}
 
-	cmd.Flags().StringVarP(&configPath, "config-path", "c", "", "configuration file path for pixiu-ingress-controller")
-	cmd.Flags().StringVar(&cfg.MetricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
+	cmd.Flags().StringVarP(&configPath, "config-path", "c", "", "configuration file path for pixiu-gateway-controller")
+	cmd.Flags().StringVar(&config.ControllerConfig.MetricsAddr, "metrics-bind-address", "0", "The address the metrics endpoint binds to. "+
 		"Use :8443 for HTTPS or :8080 for HTTP, or leave as 0 to disable the metrics service.")
-	cmd.Flags().StringVar(&cfg.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
-	cmd.Flags().StringVar(&cfg.LogLevel, "log-level", config.DefaultLogLevel, "The log level for pixiu-ingress-controller")
-	cmd.Flags().StringVar(&cfg.ControllerName, "controller-name", config.DefaultControllerName, "The name of the controller")
+	cmd.Flags().StringVar(&config.ControllerConfig.ProbeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
+	cmd.Flags().StringVar(&config.ControllerConfig.LogLevel, "log-level", config.DefaultLogLevel, "The log level for pixiu-gateway-controller")
+	cmd.Flags().StringVar(&config.ControllerConfig.ControllerName, "controller-name", config.DefaultControllerName, "The name of the controller")
 
 	return cmd
 }
diff --git a/controllers/config/crd/bases/pixiu.apache.org_gatewayproxies.yaml b/controllers/config/crd/bases/pixiu.apache.org_gatewayproxies.yaml
deleted file mode 100644
index cb4ca3a0..00000000
--- a/controllers/config/crd/bases/pixiu.apache.org_gatewayproxies.yaml
+++ /dev/null
@@ -1,48 +0,0 @@
----
-apiVersion: apiextensions.k8s.io/v1
-kind: CustomResourceDefinition
-metadata:
-  annotations:
-    controller-gen.kubebuilder.io/version: v0.18.0
-  name: gatewayproxies.pixiu.apache.org
-spec:
-  group: pixiu.apache.org
-  names:
-    kind: GatewayProxy
-    listKind: GatewayProxyList
-    plural: gatewayproxies
-    singular: gatewayproxy
-  scope: Namespaced
-  versions:
-  - name: v1alpha1
-    schema:
-      openAPIV3Schema:
-        description: GatewayProxy is the Schema for the gatewayproxies API
-        properties:
-          apiVersion:
-            description: |-
-              APIVersion defines the versioned schema of this representation of an object.
-              Servers should convert recognized schemas to the latest internal value, and
-              may reject unrecognized values.
-              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
-            type: string
-          kind:
-            description: |-
-              Kind is a string value representing the REST resource this object represents.
-              Servers may infer this from the endpoint the client submits requests to.
-              Cannot be updated.
-              In CamelCase.
-              More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
-            type: string
-          metadata:
-            type: object
-          spec:
-            description: |-
-              GatewayProxySpec defines configuration of gateway proxy instances,
-              including networking settings, global plugins, and plugin metadata.
-            type: object
-        type: object
-    served: true
-    storage: true
-    subresources:
-      status: {}
diff --git a/controllers/config/crd/kustomization.yaml b/controllers/config/crd/kustomization.yaml
deleted file mode 100644
index 1c339098..00000000
--- a/controllers/config/crd/kustomization.yaml
+++ /dev/null
@@ -1,16 +0,0 @@
-# This kustomization.yaml is not intended to be run by itself,
-# since it depends on service name and namespace that are out of this kustomize package.
-# It should be run by config/default
-resources:
-- bases/pixiu.apache.org_gatewayproxies.yaml
-# +kubebuilder:scaffold:crdkustomizeresource
-
-patches:
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix.
-# patches here are for enabling the conversion webhook for each CRD
-# +kubebuilder:scaffold:crdkustomizewebhookpatch
-
-# [WEBHOOK] To enable webhook, uncomment the following section
-# the following config is for teaching kustomize how to do kustomization for CRDs.
-#configurations:
-#- kustomizeconfig.yaml
diff --git a/controllers/config/crd/kustomizeconfig.yaml b/controllers/config/crd/kustomizeconfig.yaml
deleted file mode 100644
index ec5c150a..00000000
--- a/controllers/config/crd/kustomizeconfig.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
-# This file is for teaching kustomize how to substitute name and namespace reference in CRD
-nameReference:
-- kind: Service
-  version: v1
-  fieldSpecs:
-  - kind: CustomResourceDefinition
-    version: v1
-    group: apiextensions.k8s.io
-    path: spec/conversion/webhook/clientConfig/service/name
-
-namespace:
-- kind: CustomResourceDefinition
-  version: v1
-  group: apiextensions.k8s.io
-  path: spec/conversion/webhook/clientConfig/service/namespace
-  create: false
-
-varReference:
-- path: metadata/annotations
diff --git a/controllers/config/default/cert_metrics_manager_patch.yaml b/controllers/config/default/cert_metrics_manager_patch.yaml
deleted file mode 100644
index d9750155..00000000
--- a/controllers/config/default/cert_metrics_manager_patch.yaml
+++ /dev/null
@@ -1,30 +0,0 @@
-# This patch adds the args, volumes, and ports to allow the manager to use the metrics-server certs.
-
-# Add the volumeMount for the metrics-server certs
-- op: add
-  path: /spec/template/spec/containers/0/volumeMounts/-
-  value:
-    mountPath: /tmp/k8s-metrics-server/metrics-certs
-    name: metrics-certs
-    readOnly: true
-
-# Add the --metrics-cert-path argument for the metrics server
-- op: add
-  path: /spec/template/spec/containers/0/args/-
-  value: --metrics-cert-path=/tmp/k8s-metrics-server/metrics-certs
-
-# Add the metrics-server certs volume configuration
-- op: add
-  path: /spec/template/spec/volumes/-
-  value:
-    name: metrics-certs
-    secret:
-      secretName: metrics-server-cert
-      optional: false
-      items:
-        - key: ca.crt
-          path: ca.crt
-        - key: tls.crt
-          path: tls.crt
-        - key: tls.key
-          path: tls.key
diff --git a/controllers/config/default/kustomization.yaml b/controllers/config/default/kustomization.yaml
deleted file mode 100644
index 902309e8..00000000
--- a/controllers/config/default/kustomization.yaml
+++ /dev/null
@@ -1,234 +0,0 @@
-# Adds namespace to all resources.
-namespace: controllers-system
-
-# Value of this field is prepended to the
-# names of all resources, e.g. a deployment named
-# "wordpress" becomes "alices-wordpress".
-# Note that it should also match with the prefix (text before '-') of the namespace
-# field above.
-namePrefix: controllers-
-
-# Labels to add to all resources and selectors.
-#labels:
-#- includeSelectors: true
-#  pairs:
-#    someName: someValue
-
-resources:
-- ../crd
-- ../rbac
-- ../manager
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
-# crd/kustomization.yaml
-#- ../webhook
-# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
-#- ../certmanager
-# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
-#- ../prometheus
-# [METRICS] Expose the controller manager metrics service.
-- metrics_service.yaml
-# [NETWORK POLICY] Protect the /metrics endpoint and Webhook Server with NetworkPolicy.
-# Only Pod(s) running a namespace labeled with 'metrics: enabled' will be able to gather the metrics.
-# Only CR(s) which requires webhooks and are applied on namespaces labeled with 'webhooks: enabled' will
-# be able to communicate with the Webhook Server.
-#- ../network-policy
-
-# Uncomment the patches line if you enable Metrics
-patches:
-# [METRICS] The following patch will enable the metrics endpoint using HTTPS and the port :8443.
-# More info: https://book.kubebuilder.io/reference/metrics
-- path: manager_metrics_patch.yaml
-  target:
-    kind: Deployment
-
-# Uncomment the patches line if you enable Metrics and CertManager
-# [METRICS-WITH-CERTS] To enable metrics protected with certManager, uncomment the following line.
-# This patch will protect the metrics with certManager self-signed certs.
-#- path: cert_metrics_manager_patch.yaml
-#  target:
-#    kind: Deployment
-
-# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
-# crd/kustomization.yaml
-#- path: manager_webhook_patch.yaml
-#  target:
-#    kind: Deployment
-
-# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
-# Uncomment the following replacements to add the cert-manager CA injection annotations
-#replacements:
-# - source: # Uncomment the following block to enable certificates for metrics
-#     kind: Service
-#     version: v1
-#     name: controller-manager-metrics-service
-#     fieldPath: metadata.name
-#   targets:
-#     - select:
-#         kind: Certificate
-#         group: cert-manager.io
-#         version: v1
-#         name: metrics-certs
-#       fieldPaths:
-#         - spec.dnsNames.0
-#         - spec.dnsNames.1
-#       options:
-#         delimiter: '.'
-#         index: 0
-#         create: true
-#     - select: # Uncomment the following to set the Service name for TLS config in Prometheus ServiceMonitor
-#         kind: ServiceMonitor
-#         group: monitoring.coreos.com
-#         version: v1
-#         name: controller-manager-metrics-monitor
-#       fieldPaths:
-#         - spec.endpoints.0.tlsConfig.serverName
-#       options:
-#         delimiter: '.'
-#         index: 0
-#         create: true
-
-# - source:
-#     kind: Service
-#     version: v1
-#     name: controller-manager-metrics-service
-#     fieldPath: metadata.namespace
-#   targets:
-#     - select:
-#         kind: Certificate
-#         group: cert-manager.io
-#         version: v1
-#         name: metrics-certs
-#       fieldPaths:
-#         - spec.dnsNames.0
-#         - spec.dnsNames.1
-#       options:
-#         delimiter: '.'
-#         index: 1
-#         create: true
-#     - select: # Uncomment the following to set the Service namespace for TLS in Prometheus ServiceMonitor
-#         kind: ServiceMonitor
-#         group: monitoring.coreos.com
-#         version: v1
-#         name: controller-manager-metrics-monitor
-#       fieldPaths:
-#         - spec.endpoints.0.tlsConfig.serverName
-#       options:
-#         delimiter: '.'
-#         index: 1
-#         create: true
-
-# - source: # Uncomment the following block if you have any webhook
-#     kind: Service
-#     version: v1
-#     name: webhook-service
-#     fieldPath: .metadata.name # Name of the service
-#   targets:
-#     - select:
-#         kind: Certificate
-#         group: cert-manager.io
-#         version: v1
-#         name: serving-cert
-#       fieldPaths:
-#         - .spec.dnsNames.0
-#         - .spec.dnsNames.1
-#       options:
-#         delimiter: '.'
-#         index: 0
-#         create: true
-# - source:
-#     kind: Service
-#     version: v1
-#     name: webhook-service
-#     fieldPath: .metadata.namespace # Namespace of the service
-#   targets:
-#     - select:
-#         kind: Certificate
-#         group: cert-manager.io
-#         version: v1
-#         name: serving-cert
-#       fieldPaths:
-#         - .spec.dnsNames.0
-#         - .spec.dnsNames.1
-#       options:
-#         delimiter: '.'
-#         index: 1
-#         create: true
-
-# - source: # Uncomment the following block if you have a ValidatingWebhook (--programmatic-validation)
-#     kind: Certificate
-#     group: cert-manager.io
-#     version: v1
-#     name: serving-cert # This name should match the one in certificate.yaml
-#     fieldPath: .metadata.namespace # Namespace of the certificate CR
-#   targets:
-#     - select:
-#         kind: ValidatingWebhookConfiguration
-#       fieldPaths:
-#         - .metadata.annotations.[cert-manager.io/inject-ca-from]
-#       options:
-#         delimiter: '/'
-#         index: 0
-#         create: true
-# - source:
-#     kind: Certificate
-#     group: cert-manager.io
-#     version: v1
-#     name: serving-cert
-#     fieldPath: .metadata.name
-#   targets:
-#     - select:
-#         kind: ValidatingWebhookConfiguration
-#       fieldPaths:
-#         - .metadata.annotations.[cert-manager.io/inject-ca-from]
-#       options:
-#         delimiter: '/'
-#         index: 1
-#         create: true
-
-# - source: # Uncomment the following block if you have a DefaultingWebhook (--defaulting )
-#     kind: Certificate
-#     group: cert-manager.io
-#     version: v1
-#     name: serving-cert
-#     fieldPath: .metadata.namespace # Namespace of the certificate CR
-#   targets:
-#     - select:
-#         kind: MutatingWebhookConfiguration
-#       fieldPaths:
-#         - .metadata.annotations.[cert-manager.io/inject-ca-from]
-#       options:
-#         delimiter: '/'
-#         index: 0
-#         create: true
-# - source:
-#     kind: Certificate
-#     group: cert-manager.io
-#     version: v1
-#     name: serving-cert
-#     fieldPath: .metadata.name
-#   targets:
-#     - select:
-#         kind: MutatingWebhookConfiguration
-#       fieldPaths:
-#         - .metadata.annotations.[cert-manager.io/inject-ca-from]
-#       options:
-#         delimiter: '/'
-#         index: 1
-#         create: true
-
-# - source: # Uncomment the following block if you have a ConversionWebhook (--conversion)
-#     kind: Certificate
-#     group: cert-manager.io
-#     version: v1
-#     name: serving-cert
-#     fieldPath: .metadata.namespace # Namespace of the certificate CR
-#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.
-# +kubebuilder:scaffold:crdkustomizecainjectionns
-# - source:
-#     kind: Certificate
-#     group: cert-manager.io
-#     version: v1
-#     name: serving-cert
-#     fieldPath: .metadata.name
-#   targets: # Do not remove or uncomment the following scaffold marker; required to generate code for target CRD.
-# +kubebuilder:scaffold:crdkustomizecainjectionname
diff --git a/controllers/config/default/manager_metrics_patch.yaml b/controllers/config/default/manager_metrics_patch.yaml
deleted file mode 100644
index 2aaef653..00000000
--- a/controllers/config/default/manager_metrics_patch.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-# This patch adds the args to allow exposing the metrics endpoint using HTTPS
-- op: add
-  path: /spec/template/spec/containers/0/args/0
-  value: --metrics-bind-address=:8443
diff --git a/controllers/config/default/metrics_service.yaml b/controllers/config/default/metrics_service.yaml
deleted file mode 100644
index cbd92de9..00000000
--- a/controllers/config/default/metrics_service.yaml
+++ /dev/null
@@ -1,18 +0,0 @@
-apiVersion: v1
-kind: Service
-metadata:
-  labels:
-    control-plane: controller-manager
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: controller-manager-metrics-service
-  namespace: system
-spec:
-  ports:
-  - name: https
-    port: 8443
-    protocol: TCP
-    targetPort: 8443
-  selector:
-    control-plane: controller-manager
-    app.kubernetes.io/name: controllers
diff --git a/controllers/config/manager/kustomization.yaml b/controllers/config/manager/kustomization.yaml
deleted file mode 100644
index 5c5f0b84..00000000
--- a/controllers/config/manager/kustomization.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-resources:
-- manager.yaml
diff --git a/controllers/config/manager/manager.yaml b/controllers/config/manager/manager.yaml
deleted file mode 100644
index e94d0bfb..00000000
--- a/controllers/config/manager/manager.yaml
+++ /dev/null
@@ -1,99 +0,0 @@
-apiVersion: v1
-kind: Namespace
-metadata:
-  labels:
-    control-plane: controller-manager
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: system
----
-apiVersion: apps/v1
-kind: Deployment
-metadata:
-  name: controller-manager
-  namespace: system
-  labels:
-    control-plane: controller-manager
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-spec:
-  selector:
-    matchLabels:
-      control-plane: controller-manager
-      app.kubernetes.io/name: controllers
-  replicas: 1
-  template:
-    metadata:
-      annotations:
-        kubectl.kubernetes.io/default-container: manager
-      labels:
-        control-plane: controller-manager
-        app.kubernetes.io/name: controllers
-    spec:
-      # TODO(user): Uncomment the following code to configure the nodeAffinity expression
-      # according to the platforms which are supported by your solution.
-      # It is considered best practice to support multiple architectures. You can
-      # build your manager image using the makefile target docker-buildx.
-      # affinity:
-      #   nodeAffinity:
-      #     requiredDuringSchedulingIgnoredDuringExecution:
-      #       nodeSelectorTerms:
-      #         - matchExpressions:
-      #           - key: kubernetes.io/arch
-      #             operator: In
-      #             values:
-      #               - amd64
-      #               - arm64
-      #               - ppc64le
-      #               - s390x
-      #           - key: kubernetes.io/os
-      #             operator: In
-      #             values:
-      #               - linux
-      securityContext:
-        # Projects are configured by default to adhere to the "restricted" Pod Security Standards.
-        # This ensures that deployments meet the highest security requirements for Kubernetes.
-        # For more details, see: https://kubernetes.io/docs/concepts/security/pod-security-standards/#restricted
-        runAsNonRoot: true
-        seccompProfile:
-          type: RuntimeDefault
-      containers:
-      - command:
-        - /manager
-        args:
-          - --leader-elect
-          - --health-probe-bind-address=:8081
-        image: controller:latest
-        name: manager
-        ports: []
-        securityContext:
-          readOnlyRootFilesystem: true
-          allowPrivilegeEscalation: false
-          capabilities:
-            drop:
-            - "ALL"
-        livenessProbe:
-          httpGet:
-            path: /healthz
-            port: 8081
-          initialDelaySeconds: 15
-          periodSeconds: 20
-        readinessProbe:
-          httpGet:
-            path: /readyz
-            port: 8081
-          initialDelaySeconds: 5
-          periodSeconds: 10
-        # TODO(user): Configure the resources accordingly based on the project requirements.
-        # More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
-        resources:
-          limits:
-            cpu: 500m
-            memory: 128Mi
-          requests:
-            cpu: 10m
-            memory: 64Mi
-        volumeMounts: []
-      volumes: []
-      serviceAccountName: controller-manager
-      terminationGracePeriodSeconds: 10
diff --git a/controllers/config/network-policy/allow-metrics-traffic.yaml b/controllers/config/network-policy/allow-metrics-traffic.yaml
deleted file mode 100644
index d784b901..00000000
--- a/controllers/config/network-policy/allow-metrics-traffic.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-# This NetworkPolicy allows ingress traffic
-# with Pods running on namespaces labeled with 'metrics: enabled'. Only Pods on those
-# namespaces are able to gather data from the metrics endpoint.
-apiVersion: networking.k8s.io/v1
-kind: NetworkPolicy
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: allow-metrics-traffic
-  namespace: system
-spec:
-  podSelector:
-    matchLabels:
-      control-plane: controller-manager
-      app.kubernetes.io/name: controllers
-  policyTypes:
-    - Ingress
-  ingress:
-    # This allows ingress traffic from any namespace with the label metrics: enabled
-    - from:
-      - namespaceSelector:
-          matchLabels:
-            metrics: enabled  # Only from namespaces with this label
-      ports:
-        - port: 8443
-          protocol: TCP
diff --git a/controllers/config/network-policy/kustomization.yaml b/controllers/config/network-policy/kustomization.yaml
deleted file mode 100644
index ec0fb5e5..00000000
--- a/controllers/config/network-policy/kustomization.yaml
+++ /dev/null
@@ -1,2 +0,0 @@
-resources:
-- allow-metrics-traffic.yaml
diff --git a/controllers/config/prometheus/kustomization.yaml b/controllers/config/prometheus/kustomization.yaml
deleted file mode 100644
index fdc5481b..00000000
--- a/controllers/config/prometheus/kustomization.yaml
+++ /dev/null
@@ -1,11 +0,0 @@
-resources:
-- monitor.yaml
-
-# [PROMETHEUS-WITH-CERTS] The following patch configures the ServiceMonitor in ../prometheus
-# to securely reference certificates created and managed by cert-manager.
-# Additionally, ensure that you uncomment the [METRICS WITH CERTMANAGER] patch under config/default/kustomization.yaml
-# to mount the "metrics-server-cert" secret in the Manager Deployment.
-#patches:
-#  - path: monitor_tls_patch.yaml
-#    target:
-#      kind: ServiceMonitor
diff --git a/controllers/config/prometheus/monitor.yaml b/controllers/config/prometheus/monitor.yaml
deleted file mode 100644
index 4779ceb2..00000000
--- a/controllers/config/prometheus/monitor.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-# Prometheus Monitor Service (Metrics)
-apiVersion: monitoring.coreos.com/v1
-kind: ServiceMonitor
-metadata:
-  labels:
-    control-plane: controller-manager
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: controller-manager-metrics-monitor
-  namespace: system
-spec:
-  endpoints:
-    - path: /metrics
-      port: https # Ensure this is the name of the port that exposes HTTPS metrics
-      scheme: https
-      bearerTokenFile: /var/run/secrets/kubernetes.io/serviceaccount/token
-      tlsConfig:
-        # TODO(user): The option insecureSkipVerify: true is not recommended for production since it disables
-        # certificate verification, exposing the system to potential man-in-the-middle attacks.
-        # For production environments, it is recommended to use cert-manager for automatic TLS certificate management.
-        # To apply this configuration, enable cert-manager and use the patch located at config/prometheus/servicemonitor_tls_patch.yaml,
-        # which securely references the certificate from the 'metrics-server-cert' secret.
-        insecureSkipVerify: true
-  selector:
-    matchLabels:
-      control-plane: controller-manager
-      app.kubernetes.io/name: controllers
diff --git a/controllers/config/prometheus/monitor_tls_patch.yaml b/controllers/config/prometheus/monitor_tls_patch.yaml
deleted file mode 100644
index 5bf84ce0..00000000
--- a/controllers/config/prometheus/monitor_tls_patch.yaml
+++ /dev/null
@@ -1,19 +0,0 @@
-# Patch for Prometheus ServiceMonitor to enable secure TLS configuration
-# using certificates managed by cert-manager
-- op: replace
-  path: /spec/endpoints/0/tlsConfig
-  value:
-    # SERVICE_NAME and SERVICE_NAMESPACE will be substituted by kustomize
-    serverName: SERVICE_NAME.SERVICE_NAMESPACE.svc
-    insecureSkipVerify: false
-    ca:
-      secret:
-        name: metrics-server-cert
-        key: ca.crt
-    cert:
-      secret:
-        name: metrics-server-cert
-        key: tls.crt
-    keySecret:
-      name: metrics-server-cert
-      key: tls.key
diff --git a/controllers/config/rbac/gatewayproxy_admin_role.yaml b/controllers/config/rbac/gatewayproxy_admin_role.yaml
deleted file mode 100644
index 79b57562..00000000
--- a/controllers/config/rbac/gatewayproxy_admin_role.yaml
+++ /dev/null
@@ -1,27 +0,0 @@
-# This rule is not used by the project controllers itself.
-# It is provided to allow the cluster admin to help manage permissions for users.
-#
-# Grants full permissions ('*') over pixiu.apache.org.
-# This role is intended for users authorized to modify roles and bindings within the cluster,
-# enabling them to delegate specific permissions to other users or groups as needed.
-
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: gatewayproxy-admin-role
-rules:
-- apiGroups:
-  - pixiu.apache.org
-  resources:
-  - gatewayproxies
-  verbs:
-  - '*'
-- apiGroups:
-  - pixiu.apache.org
-  resources:
-  - gatewayproxies/status
-  verbs:
-  - get
diff --git a/controllers/config/rbac/gatewayproxy_editor_role.yaml b/controllers/config/rbac/gatewayproxy_editor_role.yaml
deleted file mode 100644
index 5f8afe79..00000000
--- a/controllers/config/rbac/gatewayproxy_editor_role.yaml
+++ /dev/null
@@ -1,33 +0,0 @@
-# This rule is not used by the project controllers itself.
-# It is provided to allow the cluster admin to help manage permissions for users.
-#
-# Grants permissions to create, update, and delete resources within the pixiu.apache.org.
-# This role is intended for users who need to manage these resources
-# but should not control RBAC or manage permissions for others.
-
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: gatewayproxy-editor-role
-rules:
-- apiGroups:
-  - pixiu.apache.org
-  resources:
-  - gatewayproxies
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - pixiu.apache.org
-  resources:
-  - gatewayproxies/status
-  verbs:
-  - get
diff --git a/controllers/config/rbac/gatewayproxy_viewer_role.yaml b/controllers/config/rbac/gatewayproxy_viewer_role.yaml
deleted file mode 100644
index 0a50c6fa..00000000
--- a/controllers/config/rbac/gatewayproxy_viewer_role.yaml
+++ /dev/null
@@ -1,29 +0,0 @@
-# This rule is not used by the project controllers itself.
-# It is provided to allow the cluster admin to help manage permissions for users.
-#
-# Grants read-only access to pixiu.apache.org resources.
-# This role is intended for users who need visibility into these resources
-# without permissions to modify them. It is ideal for monitoring purposes and limited-access viewing.
-
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: gatewayproxy-viewer-role
-rules:
-- apiGroups:
-  - pixiu.apache.org
-  resources:
-  - gatewayproxies
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - pixiu.apache.org
-  resources:
-  - gatewayproxies/status
-  verbs:
-  - get
diff --git a/controllers/config/rbac/kustomization.yaml b/controllers/config/rbac/kustomization.yaml
deleted file mode 100644
index 10206ddb..00000000
--- a/controllers/config/rbac/kustomization.yaml
+++ /dev/null
@@ -1,28 +0,0 @@
-resources:
-# All RBAC will be applied under this service account in
-# the deployment namespace. You may comment out this resource
-# if your manager will use a service account that exists at
-# runtime. Be sure to update RoleBinding and ClusterRoleBinding
-# subjects if changing service account names.
-- service_account.yaml
-- role.yaml
-- role_binding.yaml
-- leader_election_role.yaml
-- leader_election_role_binding.yaml
-# The following RBAC configurations are used to protect
-# the metrics endpoint with authn/authz. These configurations
-# ensure that only authorized users and service accounts
-# can access the metrics endpoint. Comment the following
-# permissions if you want to disable this protection.
-# More info: https://book.kubebuilder.io/reference/metrics.html
-- metrics_auth_role.yaml
-- metrics_auth_role_binding.yaml
-- metrics_reader_role.yaml
-# For each CRD, "Admin", "Editor" and "Viewer" roles are scaffolded by
-# default, aiding admins in cluster management. Those roles are
-# not used by the controllers itself. You can comment the following lines
-# if you do not want those helpers be installed with your Project.
-- gatewayproxy_admin_role.yaml
-- gatewayproxy_editor_role.yaml
-- gatewayproxy_viewer_role.yaml
-
diff --git a/controllers/config/rbac/leader_election_role.yaml b/controllers/config/rbac/leader_election_role.yaml
deleted file mode 100644
index 1cc9d1af..00000000
--- a/controllers/config/rbac/leader_election_role.yaml
+++ /dev/null
@@ -1,40 +0,0 @@
-# permissions to do leader election.
-apiVersion: rbac.authorization.k8s.io/v1
-kind: Role
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: leader-election-role
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - configmaps
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - patch
-  - delete
-- apiGroups:
-  - coordination.k8s.io
-  resources:
-  - leases
-  verbs:
-  - get
-  - list
-  - watch
-  - create
-  - update
-  - patch
-  - delete
-- apiGroups:
-  - ""
-  resources:
-  - events
-  verbs:
-  - create
-  - patch
diff --git a/controllers/config/rbac/leader_election_role_binding.yaml b/controllers/config/rbac/leader_election_role_binding.yaml
deleted file mode 100644
index 41b766b6..00000000
--- a/controllers/config/rbac/leader_election_role_binding.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: RoleBinding
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: leader-election-rolebinding
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: Role
-  name: leader-election-role
-subjects:
-- kind: ServiceAccount
-  name: controller-manager
-  namespace: system
diff --git a/controllers/config/rbac/metrics_auth_role.yaml b/controllers/config/rbac/metrics_auth_role.yaml
deleted file mode 100644
index 32d2e4ec..00000000
--- a/controllers/config/rbac/metrics_auth_role.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: metrics-auth-role
-rules:
-- apiGroups:
-  - authentication.k8s.io
-  resources:
-  - tokenreviews
-  verbs:
-  - create
-- apiGroups:
-  - authorization.k8s.io
-  resources:
-  - subjectaccessreviews
-  verbs:
-  - create
diff --git a/controllers/config/rbac/metrics_auth_role_binding.yaml b/controllers/config/rbac/metrics_auth_role_binding.yaml
deleted file mode 100644
index e775d67f..00000000
--- a/controllers/config/rbac/metrics_auth_role_binding.yaml
+++ /dev/null
@@ -1,12 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  name: metrics-auth-rolebinding
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: metrics-auth-role
-subjects:
-- kind: ServiceAccount
-  name: controller-manager
-  namespace: system
diff --git a/controllers/config/rbac/metrics_reader_role.yaml b/controllers/config/rbac/metrics_reader_role.yaml
deleted file mode 100644
index 51a75db4..00000000
--- a/controllers/config/rbac/metrics_reader_role.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: metrics-reader
-rules:
-- nonResourceURLs:
-  - "/metrics"
-  verbs:
-  - get
diff --git a/controllers/config/rbac/role.yaml b/controllers/config/rbac/role.yaml
deleted file mode 100644
index 8b3db158..00000000
--- a/controllers/config/rbac/role.yaml
+++ /dev/null
@@ -1,103 +0,0 @@
----
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRole
-metadata:
-  name: manager-role
-rules:
-- apiGroups:
-  - ""
-  resources:
-  - events
-  verbs:
-  - create
-  - patch
-- apiGroups:
-  - ""
-  resources:
-  - namespaces
-  - pods
-  - services
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - coordination.k8s.io
-  resources:
-  - leases
-  verbs:
-  - create
-  - delete
-  - get
-  - list
-  - patch
-  - update
-  - watch
-- apiGroups:
-  - discovery.k8s.io
-  resources:
-  - endpointslices
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - gatewayclasses
-  - gateways
-  verbs:
-  - get
-  - list
-  - update
-  - watch
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - gatewayclasses/status
-  - gateways/status
-  - httproutes/status
-  - referencegrants/status
-  verbs:
-  - get
-  - update
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - httproutes
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - gateway.networking.k8s.io
-  resources:
-  - referencegrants
-  verbs:
-  - list
-  - update
-  - watch
-- apiGroups:
-  - networking.k8s.io
-  resources:
-  - ingressclasses
-  verbs:
-  - get
-  - list
-  - watch
-- apiGroups:
-  - networking.k8s.io
-  resources:
-  - ingresses
-  verbs:
-  - get
-  - list
-  - update
-  - watch
-- apiGroups:
-  - networking.k8s.io
-  resources:
-  - ingresses/status
-  verbs:
-  - get
-  - update
diff --git a/controllers/config/rbac/role_binding.yaml b/controllers/config/rbac/role_binding.yaml
deleted file mode 100644
index 506f311e..00000000
--- a/controllers/config/rbac/role_binding.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
-apiVersion: rbac.authorization.k8s.io/v1
-kind: ClusterRoleBinding
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: manager-rolebinding
-roleRef:
-  apiGroup: rbac.authorization.k8s.io
-  kind: ClusterRole
-  name: manager-role
-subjects:
-- kind: ServiceAccount
-  name: controller-manager
-  namespace: system
diff --git a/controllers/config/rbac/service_account.yaml b/controllers/config/rbac/service_account.yaml
deleted file mode 100644
index c6b102b9..00000000
--- a/controllers/config/rbac/service_account.yaml
+++ /dev/null
@@ -1,8 +0,0 @@
-apiVersion: v1
-kind: ServiceAccount
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: controller-manager
-  namespace: system
diff --git a/controllers/config/samples/kustomization.yaml b/controllers/config/samples/kustomization.yaml
deleted file mode 100644
index 463a663a..00000000
--- a/controllers/config/samples/kustomization.yaml
+++ /dev/null
@@ -1,4 +0,0 @@
-## Append samples of your project ##
-resources:
-- v1alpha1_gatewayproxy.yaml
-# +kubebuilder:scaffold:manifestskustomizesamples
diff --git a/controllers/config/samples/v1alpha1_gatewayproxy.yaml b/controllers/config/samples/v1alpha1_gatewayproxy.yaml
deleted file mode 100644
index b10e1bfc..00000000
--- a/controllers/config/samples/v1alpha1_gatewayproxy.yaml
+++ /dev/null
@@ -1,9 +0,0 @@
-apiVersion: pixiu.apache.org/v1alpha1
-kind: GatewayProxy
-metadata:
-  labels:
-    app.kubernetes.io/name: controllers
-    app.kubernetes.io/managed-by: kustomize
-  name: gatewayproxy-sample
-spec:
-  # TODO(user): Add fields here
diff --git a/controllers/go.mod b/controllers/go.mod
index bd6c1958..cdc101f0 100644
--- a/controllers/go.mod
+++ b/controllers/go.mod
@@ -3,14 +3,17 @@ module controllers
 go 1.24.0
 
 require (
+	github.com/dubbo-go-pixiu/pixiu-api v0.0.0-00010101000000-000000000000
+	github.com/envoyproxy/go-control-plane v0.14.0
+	github.com/envoyproxy/go-control-plane/envoy v1.36.0
 	github.com/go-logr/logr v1.4.3
 	github.com/go-logr/zapr v1.3.0
 	github.com/google/go-cmp v0.7.0
-	github.com/onsi/ginkgo/v2 v2.22.0
-	github.com/onsi/gomega v1.36.1
 	github.com/samber/lo v1.52.0
 	github.com/spf13/cobra v1.9.1
 	go.uber.org/zap v1.27.0
+	google.golang.org/grpc v1.75.1
+	google.golang.org/protobuf v1.36.10
 	gopkg.in/yaml.v3 v3.0.1
 	k8s.io/api v0.34.1
 	k8s.io/apimachinery v0.34.1
@@ -18,6 +21,7 @@ require (
 	k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d
 	sigs.k8s.io/controller-runtime v0.22.1
 	sigs.k8s.io/gateway-api v1.4.0
+	sigs.k8s.io/yaml v1.6.0
 )
 
 require (
@@ -27,8 +31,11 @@ require (
 	github.com/blang/semver/v4 v4.0.0 // indirect
 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect
 	github.com/cespare/xxhash/v2 v2.3.0 // indirect
+	github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/emicklei/go-restful/v3 v3.13.0 // indirect
+	github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 // indirect
+	github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
 	github.com/evanphx/json-patch/v5 v5.9.11 // indirect
 	github.com/felixge/httpsnoop v1.0.4 // indirect
 	github.com/fsnotify/fsnotify v1.9.0 // indirect
@@ -37,14 +44,12 @@ require (
 	github.com/go-openapi/jsonpointer v0.21.2 // indirect
 	github.com/go-openapi/jsonreference v0.21.0 // indirect
 	github.com/go-openapi/swag v0.23.1 // indirect
-	github.com/go-task/slim-sprig/v3 v3.0.0 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
 	github.com/google/btree v1.1.3 // indirect
 	github.com/google/cel-go v0.26.0 // indirect
 	github.com/google/gnostic-models v0.7.0 // indirect
-	github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db // indirect
 	github.com/google/uuid v1.6.0 // indirect
-	github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect
+	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
@@ -52,6 +57,7 @@ require (
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
+	github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
 	github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
 	github.com/prometheus/client_golang v1.23.0 // indirect
 	github.com/prometheus/client_model v0.6.2 // indirect
@@ -68,7 +74,7 @@ require (
 	go.opentelemetry.io/otel/metric v1.37.0 // indirect
 	go.opentelemetry.io/otel/sdk v1.37.0 // indirect
 	go.opentelemetry.io/otel/trace v1.37.0 // indirect
-	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
+	go.opentelemetry.io/proto/otlp v1.7.1 // indirect
 	go.uber.org/multierr v1.11.0 // indirect
 	go.yaml.in/yaml/v2 v2.4.2 // indirect
 	go.yaml.in/yaml/v3 v3.0.4 // indirect
@@ -80,12 +86,9 @@ require (
 	golang.org/x/term v0.34.0 // indirect
 	golang.org/x/text v0.28.0 // indirect
 	golang.org/x/time v0.12.0 // indirect
-	golang.org/x/tools v0.36.0 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
-	google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 // indirect
 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 // indirect
-	google.golang.org/grpc v1.75.1 // indirect
-	google.golang.org/protobuf v1.36.8 // indirect
 	gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
 	k8s.io/apiextensions-apiserver v0.34.1 // indirect
@@ -97,5 +100,6 @@ require (
 	sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect
 	sigs.k8s.io/randfill v1.0.0 // indirect
 	sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
-	sigs.k8s.io/yaml v1.6.0 // indirect
 )
+
+replace github.com/dubbo-go-pixiu/pixiu-api => github.com/dubbogo/dubbo-go-pixiu-filter v0.1.5
diff --git a/controllers/go.sum b/controllers/go.sum
index 616751ef..9b0b51e2 100644
--- a/controllers/go.sum
+++ b/controllers/go.sum
@@ -10,13 +10,25 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3
 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
 github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
 github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls=
+github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8=
 github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dubbogo/dubbo-go-pixiu-filter v0.1.5 h1:qwzUNqWdLjE3+in49/Ny1Vjfzrztzd3JcxdUenDe610=
+github.com/dubbogo/dubbo-go-pixiu-filter v0.1.5/go.mod h1:sZSi0hyl9rquVwHZQxNa8ARs85/gQAwzjYgbxFLP4yY=
 github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes=
 github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
+github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA=
+github.com/envoyproxy/go-control-plane v0.14.0/go.mod h1:NcS5X47pLl/hfqxU70yPwL9ZMkUlwlKxtAohpi2wBEU=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0 h1:yg/JjO5E7ubRyKX3m07GF3reDNEnfOboJ0QySbH736g=
+github.com/envoyproxy/go-control-plane/envoy v1.36.0/go.mod h1:ty89S1YCCVruQAm9OtKeEkQLTb+Lkz0k8v9W0Oxsv98=
+github.com/envoyproxy/go-control-plane/ratelimit v0.1.0 h1:/G9QYbddjL25KvtKTv3an9lx6VBE2cnb8wp1vEGNYGI=
+github.com/envoyproxy/go-control-plane/ratelimit v0.1.0/go.mod h1:Wk+tMFAFbCXaJPzVVHnPgRKdUdwW/KdbRt94AzgRee4=
+github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
+github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
 github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k=
 github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
 github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU=
@@ -44,6 +56,7 @@ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1v
 github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
@@ -52,6 +65,7 @@ github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI=
 github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM=
 github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo=
 github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
 github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
@@ -61,8 +75,8 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db h1:097atOisP2aRj7vFgY
 github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
 github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -95,6 +109,8 @@ github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw=
 github.com/onsi/gomega v1.36.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
+github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
 github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -127,8 +143,8 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
-github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8=
-github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
+github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
+github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -151,8 +167,8 @@ go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFh
 go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
 go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
 go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
-go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
-go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
+go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
+go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
 go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -210,14 +226,16 @@ gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw
 gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
 gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
 gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
-google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
-google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0 h1:0UOBWO4dC+e51ui0NFKSPbkHHiQ4TmrEfEZMLDyRmY8=
+google.golang.org/genproto/googleapis/api v0.0.0-20250728155136-f173205681a0/go.mod h1:8ytArBbtOy2xfht+y2fqKd5DRDJRUQhqbyEnQ4bDChs=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1 h1:pmJpJEvT846VzausCQ5d7KreSROcDqmO388w5YbnltA=
 google.golang.org/genproto/googleapis/rpc v0.0.0-20250826171959-ef028d996bc1/go.mod h1:GmFNa4BdJZ2a8G+wCe9Bg3wwThLrJun751XstdJt5Og=
 google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
 google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
-google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc=
-google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
+google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
diff --git a/controllers/hack/boilerplate.go.txt b/controllers/hack/boilerplate.go.txt
index 6bf5542c..2944f981 100644
--- a/controllers/hack/boilerplate.go.txt
+++ b/controllers/hack/boilerplate.go.txt
@@ -1,15 +1,16 @@
 /*
-Copyright 2025 dubbo.apache.org.
-
-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.
-*/
\ No newline at end of file
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
diff --git a/controllers/internal/controller/config/config.go b/controllers/internal/controller/config/config.go
index 7f81413f..310f098b 100644
--- a/controllers/internal/controller/config/config.go
+++ b/controllers/internal/controller/config/config.go
@@ -28,8 +28,8 @@ var (
 )
 
 const (
-	DefaultLeaderElectionID = "pixiu-ingress-gateway-leader"
-	DefaultControllerName   = "pixiu.apache.org/pixiu-ingress-controller"
+	DefaultLeaderElectionID = "pixiu-gateway-leader"
+	DefaultControllerName   = "pixiu.apache.org/pixiu-gateway-controller"
 	DefaultLogLevel         = "info"
 	DefaultMetricsAddr      = ":8080"
 	DefaultProbeAddr        = ":8081"
diff --git a/controllers/internal/controller/gateway_controller.go b/controllers/internal/controller/gateway_controller.go
index e0fbe2a9..c23673d8 100644
--- a/controllers/internal/controller/gateway_controller.go
+++ b/controllers/internal/controller/gateway_controller.go
@@ -25,20 +25,26 @@ import (
 )
 
 import (
-	"controllers/api/v1alpha1"
-
-	"controllers/internal/controller/indexer"
 	"controllers/internal/controller/status"
 
+	"controllers/internal/converter"
+
+	"controllers/internal/ir"
+
+	"controllers/internal/translator"
+
 	"controllers/internal/utils"
 
 	"github.com/go-logr/logr"
 
+	appsv1 "k8s.io/api/apps/v1"
+
 	corev1 "k8s.io/api/core/v1"
 
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/apimachinery/pkg/util/intstr"
 
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -82,8 +88,16 @@ func (r *GatewayReconciler) SetupWithManager(mgr ctrl.Manager) error {
 			),
 		).
 		Watches(
-			&v1alpha1.GatewayProxy{},
-			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForGatewayProxy),
+			&gatewayv1.HTTPRoute{},
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForHTTPRoute),
+		).
+		Watches(
+			&appsv1.Deployment{},
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForDeployment),
+		).
+		Watches(
+			&corev1.Service{},
+			handler.EnqueueRequestsFromMapFunc(r.listGatewaysForService),
 		)
 
 	if GetEnableReferenceGrant() {
@@ -124,11 +138,21 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
 	}
 
 	r.processListenerConfig(gateway)
-	if err := r.processInfrastructure(gateway); err != nil {
-		acceptStatus = conditionStatus{
-			status: false,
-			msg:    err.Error(),
-		}
+
+	if err := r.ensureGatewayConfigMap(ctx, gateway); err != nil {
+		r.Log.Error(err, "failed to ensure gateway configmap", "gateway", gateway.GetName())
+		conditionProgrammedStatus = false
+		conditionProgrammedMsg = fmt.Sprintf("Failed to create configmap: %v", err)
+	}
+
+	if err := r.ensureDataPlane(ctx, gateway); err != nil {
+		r.Log.Error(err, "failed to ensure data plane", "gateway", gateway.GetName())
+		conditionProgrammedStatus = false
+		conditionProgrammedMsg = fmt.Sprintf("Failed to create data plane: %v", err)
+	}
+
+	if err := r.updateGatewayAddresses(ctx, gateway); err != nil {
+		r.Log.Error(err, "failed to update gateway addresses", "gateway", gateway.GetName())
 	}
 
 	listenerStatuses, err := getListenerStatus(ctx, r.Client, gateway)
@@ -139,7 +163,9 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
 
 	accepted := SetGatewayConditionAccepted(gateway, acceptStatus.status, acceptStatus.msg)
 	programmed := SetGatewayConditionProgrammed(gateway, conditionProgrammedStatus, conditionProgrammedMsg)
-	if accepted || programmed || len(listenerStatuses) > 0 {
+
+	needsUpdate := accepted || programmed || len(listenerStatuses) > 0 || len(gateway.Status.Addresses) > 0
+	if needsUpdate {
 		if len(listenerStatuses) > 0 {
 			gateway.Status.Listeners = listenerStatuses
 		}
@@ -158,8 +184,6 @@ func (r *GatewayReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ct
 				return tCopy
 			}),
 		})
-
-		return ctrl.Result{}, nil
 	}
 
 	return ctrl.Result{}, nil
@@ -197,38 +221,6 @@ func (r *GatewayReconciler) checkGatewayClass(obj client.Object) bool {
 	return matchesController(string(gatewayClass.Spec.ControllerName))
 }
 
-func (r *GatewayReconciler) listGatewaysForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request {
-	gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to GatewayProxy")
-		return nil
-	}
-	namespace := gatewayProxy.GetNamespace()
-	name := gatewayProxy.GetName()
-
-	gatewayList := &gatewayv1.GatewayList{}
-	if err := r.List(ctx, gatewayList, client.MatchingFields{
-		indexer.ParametersRef: indexer.GenIndexKey(namespace, name),
-	}); err != nil {
-		r.Log.Error(err, "failed to list gateways for gateway proxy", "gatewayproxy", gatewayProxy.GetName())
-		return nil
-	}
-
-	recs := make([]reconcile.Request, 0, len(gatewayList.Items))
-	for _, gateway := range gatewayList.Items {
-		if !r.checkGatewayClass(&gateway) {
-			continue
-		}
-		recs = append(recs, reconcile.Request{
-			NamespacedName: client.ObjectKey{
-				Namespace: gateway.GetNamespace(),
-				Name:      gateway.GetName(),
-			},
-		})
-	}
-	return recs
-}
-
 func (r *GatewayReconciler) listGatewaysForHTTPRoute(ctx context.Context, obj client.Object) []reconcile.Request {
 	httpRoute, ok := obj.(*gatewayv1.HTTPRoute)
 	if !ok {
@@ -240,15 +232,15 @@ func (r *GatewayReconciler) listGatewaysForHTTPRoute(ctx context.Context, obj cl
 		return nil
 	}
 	recs := []reconcile.Request{}
-	for _, routeParentStatus := range httpRoute.Status.Parents {
-		gatewayNamespace := httpRoute.GetNamespace()
-		parentRef := routeParentStatus.ParentRef
+	for _, parentRef := range httpRoute.Spec.ParentRefs {
 		if parentRef.Group != nil && *parentRef.Group != gatewayv1.GroupName {
 			continue
 		}
 		if parentRef.Kind != nil && *parentRef.Kind != "Gateway" {
 			continue
 		}
+
+		gatewayNamespace := httpRoute.GetNamespace()
 		if parentRef.Namespace != nil {
 			gatewayNamespace = string(*parentRef.Namespace)
 		}
@@ -312,10 +304,6 @@ func (r *GatewayReconciler) listReferenceGrantsForGateway(ctx context.Context, o
 	return requests
 }
 
-func (r *GatewayReconciler) processInfrastructure(gateway *gatewayv1.Gateway) error {
-	return ProcessGatewayProxy(r.Client, r.Log, gateway, utils.NamespacedNameKind(gateway))
-}
-
 func (r *GatewayReconciler) processListenerConfig(gateway *gatewayv1.Gateway) {
 	listeners := gateway.Spec.Listeners
 	for _, listener := range listeners {
@@ -342,3 +330,408 @@ func (r *GatewayReconciler) processListenerConfig(gateway *gatewayv1.Gateway) {
 		}
 	}
 }
+
+func (r *GatewayReconciler) ensureGatewayConfigMap(ctx context.Context, gateway *gatewayv1.Gateway) error {
+	configMapName := fmt.Sprintf("%s-config", gateway.GetName())
+
+	translator := translator.NewTranslator(r.Client, r.Log)
+	conv := converter.NewConverter()
+
+	xds, err := translator.TranslateGateway(ctx, gateway)
+	if err != nil {
+		return fmt.Errorf("failed to translate Gateway to IR: %w", err)
+	}
+
+	allClusters := []*ir.Cluster{}
+	clusterMap := make(map[string]*ir.Cluster)
+
+	var httpRouteList gatewayv1.HTTPRouteList
+	if err := r.Client.List(ctx, &httpRouteList); err == nil {
+		matchedHTTPRoutes := 0
+		for _, httpRoute := range httpRouteList.Items {
+			attached := false
+			for _, parentRef := range httpRoute.Spec.ParentRefs {
+				if parentRef.Group != nil && *parentRef.Group != gatewayv1.GroupName {
+					continue
+				}
+				if parentRef.Kind != nil && *parentRef.Kind != "Gateway" {
+					continue
+				}
+				if string(parentRef.Name) != gateway.Name {
+					continue
+				}
+				namespaceMatch := false
+				if parentRef.Namespace != nil {
+					namespaceMatch = string(*parentRef.Namespace) == gateway.Namespace
+				} else {
+					namespaceMatch = gateway.Namespace == httpRoute.Namespace
+				}
+				if !namespaceMatch {
+					continue
+				}
+				attached = true
+				break
+			}
+			if !attached {
+				continue
+			}
+
+			matchedHTTPRoutes++
+			clusters, err := translator.TranslateBackendToCluster(ctx, &httpRoute)
+			if err != nil {
+				r.Log.Error(err, "failed to translate backend to cluster", "httproute", httpRoute.Name)
+				continue
+			}
+			for _, cluster := range clusters {
+				if existing, exists := clusterMap[cluster.Name]; exists {
+					existing.Endpoints = append(existing.Endpoints, cluster.Endpoints...)
+				} else {
+					clusterMap[cluster.Name] = cluster
+					allClusters = append(allClusters, cluster)
+				}
+			}
+		}
+	} else {
+		r.Log.Error(err, "failed to list HTTPRoutes")
+	}
+
+	pixiuConfig, err := conv.ConvertIRToPixiuConfig(xds, allClusters)
+	if err != nil {
+		return fmt.Errorf("failed to convert IR to Pixiu config: %w", err)
+	}
+
+	configYAML, err := converter.ConvertToYAML(pixiuConfig)
+	if err != nil {
+		return fmt.Errorf("failed to convert config to YAML: %w", err)
+	}
+
+	existingConfigMap := &corev1.ConfigMap{}
+	err = r.Get(ctx, client.ObjectKey{
+		Name:      configMapName,
+		Namespace: gateway.GetNamespace(),
+	}, existingConfigMap)
+
+	configMap := &corev1.ConfigMap{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      configMapName,
+			Namespace: gateway.GetNamespace(),
+			Labels: map[string]string{
+				"app.kubernetes.io/managed-by":           "pg-controller",
+				"gateway.networking.k8s.io/gateway-name": gateway.GetName(),
+			},
+			OwnerReferences: []metav1.OwnerReference{
+				{
+					APIVersion: gateway.APIVersion,
+					Kind:       gateway.Kind,
+					Name:       gateway.GetName(),
+					UID:        gateway.GetUID(),
+					Controller: func() *bool { b := true; return &b }(),
+				},
+			},
+		},
+		Data: map[string]string{
+			"conf.yaml": configYAML,
+		},
+	}
+
+	if err != nil {
+		if client.IgnoreNotFound(err) == nil {
+			if err := r.Create(ctx, configMap); err != nil {
+				return fmt.Errorf("failed to create configmap: %w", err)
+			}
+			r.Log.Info("created gateway configmap", "gateway", gateway.GetName(), "configmap", configMapName)
+		} else {
+			return fmt.Errorf("failed to check configmap: %w", err)
+		}
+	} else {
+		if existingConfigMap.Data["conf.yaml"] != configYAML {
+			existingConfigMap.Data = configMap.Data
+			existingConfigMap.Labels = configMap.Labels
+			if err := r.Update(ctx, existingConfigMap); err != nil {
+				return fmt.Errorf("failed to update configmap: %w", err)
+			}
+			r.Log.Info("updated gateway configmap", "gateway", gateway.GetName(), "configmap", configMapName)
+		}
+	}
+
+	return nil
+}
+
+func (r *GatewayReconciler) ensureDataPlane(ctx context.Context, gateway *gatewayv1.Gateway) error {
+	configMapName := fmt.Sprintf("%s-config", gateway.GetName())
+	deploymentName := fmt.Sprintf("%s-%s", gateway.GetName(), string(gateway.GetUID())[:8])
+	serviceName := deploymentName
+
+	deployment := &appsv1.Deployment{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      deploymentName,
+			Namespace: gateway.GetNamespace(),
+			Labels: map[string]string{
+				"app.kubernetes.io/name":                 "pixiu-gateway",
+				"app.kubernetes.io/managed-by":           "pg-controller",
+				"gateway.networking.k8s.io/gateway-name": gateway.GetName(),
+			},
+			OwnerReferences: []metav1.OwnerReference{
+				{
+					APIVersion: gateway.APIVersion,
+					Kind:       gateway.Kind,
+					Name:       gateway.GetName(),
+					UID:        gateway.GetUID(),
+					Controller: func() *bool { b := true; return &b }(),
+				},
+			},
+		},
+		Spec: appsv1.DeploymentSpec{
+			Replicas: func() *int32 { r := int32(1); return &r }(),
+			Selector: &metav1.LabelSelector{
+				MatchLabels: map[string]string{
+					"app.kubernetes.io/name":                 "pixiu-gateway",
+					"gateway.networking.k8s.io/gateway-name": gateway.GetName(),
+				},
+			},
+			Template: corev1.PodTemplateSpec{
+				ObjectMeta: metav1.ObjectMeta{
+					Labels: map[string]string{
+						"app.kubernetes.io/name":                 "pixiu-gateway",
+						"gateway.networking.k8s.io/gateway-name": gateway.GetName(),
+					},
+				},
+				Spec: corev1.PodSpec{
+					Containers: []corev1.Container{
+						{
+							Name:  "pixiu",
+							Image: "mfordjody/pixiugateway:debug",
+							Args: []string{
+								"gateway",
+								"start",
+								"-c",
+								"/etc/pixiu/conf.yaml",
+							},
+							Ports: []corev1.ContainerPort{
+								{
+									Name:          "http",
+									ContainerPort: 8888,
+									Protocol:      corev1.ProtocolTCP,
+								},
+							},
+							VolumeMounts: []corev1.VolumeMount{
+								{
+									Name:      "config",
+									MountPath: "/etc/pixiu",
+									ReadOnly:  true,
+								},
+							},
+						},
+					},
+					Volumes: []corev1.Volume{
+						{
+							Name: "config",
+							VolumeSource: corev1.VolumeSource{
+								ConfigMap: &corev1.ConfigMapVolumeSource{
+									LocalObjectReference: corev1.LocalObjectReference{
+										Name: configMapName,
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+		},
+	}
+
+	existingDeployment := &appsv1.Deployment{}
+	err := r.Get(ctx, client.ObjectKey{
+		Name:      deploymentName,
+		Namespace: gateway.GetNamespace(),
+	}, existingDeployment)
+	if err != nil {
+		if client.IgnoreNotFound(err) == nil {
+			if err := r.Create(ctx, deployment); err != nil {
+				return fmt.Errorf("failed to create deployment: %w", err)
+			}
+			r.Log.Info("created data plane deployment", "gateway", gateway.GetName(), "deployment", deploymentName)
+		} else {
+			return fmt.Errorf("failed to get deployment: %w", err)
+		}
+	} else {
+		needsUpdate := false
+		if !reflect.DeepEqual(existingDeployment.Spec, deployment.Spec) {
+			needsUpdate = true
+		}
+		if !reflect.DeepEqual(existingDeployment.Labels, deployment.Labels) {
+			needsUpdate = true
+		}
+
+		if needsUpdate {
+			patch := client.MergeFrom(existingDeployment.DeepCopy())
+			existingDeployment.Spec = deployment.Spec
+			existingDeployment.Labels = deployment.Labels
+			if err := r.Patch(ctx, existingDeployment, patch); err != nil {
+				return fmt.Errorf("failed to patch deployment: %w", err)
+			}
+			r.Log.Info("updated data plane deployment", "gateway", gateway.GetName(), "deployment", deploymentName)
+		}
+	}
+
+	service := &corev1.Service{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      serviceName,
+			Namespace: gateway.GetNamespace(),
+			Labels: map[string]string{
+				"app.kubernetes.io/name":                 "pixiu-gateway",
+				"app.kubernetes.io/managed-by":           "pg-controller",
+				"gateway.networking.k8s.io/gateway-name": gateway.GetName(),
+			},
+			OwnerReferences: []metav1.OwnerReference{
+				{
+					APIVersion: gateway.APIVersion,
+					Kind:       gateway.Kind,
+					Name:       gateway.GetName(),
+					UID:        gateway.GetUID(),
+					Controller: func() *bool { b := true; return &b }(),
+				},
+			},
+		},
+		Spec: corev1.ServiceSpec{
+			Type: corev1.ServiceTypeLoadBalancer,
+			Selector: map[string]string{
+				"app.kubernetes.io/name":                 "pixiu-gateway",
+				"gateway.networking.k8s.io/gateway-name": gateway.GetName(),
+			},
+			Ports: []corev1.ServicePort{
+				{
+					Name:       "http",
+					Port:       80,
+					TargetPort: intstr.FromInt(8888),
+					Protocol:   corev1.ProtocolTCP,
+				},
+			},
+		},
+	}
+
+	existingService := &corev1.Service{}
+	err = r.Get(ctx, client.ObjectKey{
+		Name:      serviceName,
+		Namespace: gateway.GetNamespace(),
+	}, existingService)
+	if err != nil {
+		if client.IgnoreNotFound(err) == nil {
+			if err := r.Create(ctx, service); err != nil {
+				return fmt.Errorf("failed to create service: %w", err)
+			}
+			r.Log.Info("created data plane service", "gateway", gateway.GetName(), "service", serviceName)
+		} else {
+			return fmt.Errorf("failed to get service: %w", err)
+		}
+	} else {
+		service.Spec.ClusterIP = existingService.Spec.ClusterIP
+		service.Spec.ClusterIPs = existingService.Spec.ClusterIPs
+		existingService.Spec = service.Spec
+		existingService.Labels = service.Labels
+		if err := r.Update(ctx, existingService); err != nil {
+			return fmt.Errorf("failed to update service: %w", err)
+		}
+		r.Log.Info("updated data plane service", "gateway", gateway.GetName(), "service", serviceName)
+	}
+
+	return nil
+}
+
+func (r *GatewayReconciler) updateGatewayAddresses(ctx context.Context, gateway *gatewayv1.Gateway) error {
+	deploymentName := fmt.Sprintf("%s-%s", gateway.GetName(), string(gateway.GetUID())[:8])
+	serviceName := deploymentName
+
+	service := &corev1.Service{}
+	if err := r.Get(ctx, client.ObjectKey{
+		Name:      serviceName,
+		Namespace: gateway.GetNamespace(),
+	}, service); err != nil {
+		if client.IgnoreNotFound(err) == nil {
+			gateway.Status.Addresses = nil
+			return nil
+		}
+		return fmt.Errorf("failed to get service: %w", err)
+	}
+
+	var addresses []gatewayv1.GatewayStatusAddress
+	if service.Spec.Type == corev1.ServiceTypeLoadBalancer {
+		if len(service.Status.LoadBalancer.Ingress) > 0 {
+			for _, ingress := range service.Status.LoadBalancer.Ingress {
+				if ingress.IP != "" {
+					addresses = append(addresses, gatewayv1.GatewayStatusAddress{
+						Type:  func() *gatewayv1.AddressType { t := gatewayv1.IPAddressType; return &t }(),
+						Value: ingress.IP,
+					})
+				} else if ingress.Hostname != "" {
+					addresses = append(addresses, gatewayv1.GatewayStatusAddress{
+						Type:  func() *gatewayv1.AddressType { t := gatewayv1.HostnameAddressType; return &t }(),
+						Value: ingress.Hostname,
+					})
+				}
+			}
+		}
+	}
+
+	if len(addresses) == 0 && service.Spec.ClusterIP != "" && service.Spec.ClusterIP != "None" {
+		addresses = append(addresses, gatewayv1.GatewayStatusAddress{
+			Type:  func() *gatewayv1.AddressType { t := gatewayv1.IPAddressType; return &t }(),
+			Value: service.Spec.ClusterIP,
+		})
+	}
+
+	gateway.Status.Addresses = addresses
+	return nil
+}
+
+func (r *GatewayReconciler) listGatewaysForDeployment(ctx context.Context, obj client.Object) []reconcile.Request {
+	deployment, ok := obj.(*appsv1.Deployment)
+	if !ok {
+		return nil
+	}
+
+	if len(deployment.OwnerReferences) == 0 {
+		return nil
+	}
+
+	for _, ownerRef := range deployment.OwnerReferences {
+		if ownerRef.Kind == "Gateway" && ownerRef.APIVersion == gatewayv1.GroupVersion.String() {
+			return []reconcile.Request{
+				{
+					NamespacedName: types.NamespacedName{
+						Namespace: deployment.GetNamespace(),
+						Name:      ownerRef.Name,
+					},
+				},
+			}
+		}
+	}
+
+	return nil
+}
+
+func (r *GatewayReconciler) listGatewaysForService(ctx context.Context, obj client.Object) []reconcile.Request {
+	service, ok := obj.(*corev1.Service)
+	if !ok {
+		return nil
+	}
+
+	if len(service.OwnerReferences) == 0 {
+		return nil
+	}
+
+	for _, ownerRef := range service.OwnerReferences {
+		if ownerRef.Kind == "Gateway" && ownerRef.APIVersion == gatewayv1.GroupVersion.String() {
+			return []reconcile.Request{
+				{
+					NamespacedName: types.NamespacedName{
+						Namespace: service.GetNamespace(),
+						Name:      ownerRef.Name,
+					},
+				},
+			}
+		}
+	}
+
+	return nil
+}
diff --git a/controllers/internal/controller/gatewayclass_controller.go b/controllers/internal/controller/gatewayclass_controller.go
index 4d05768d..880c2846 100644
--- a/controllers/internal/controller/gatewayclass_controller.go
+++ b/controllers/internal/controller/gatewayclass_controller.go
@@ -115,7 +115,7 @@ func (r *GatewayClassReconciler) Reconcile(ctx context.Context, req ctrl.Request
 		Status:             meta.ConditionTrue,
 		Reason:             string(gatewayv1.GatewayClassReasonAccepted),
 		ObservedGeneration: gc.Generation,
-		Message:            "the gatewayclass has been accepted by the apisix-ingress-controller",
+		Message:            "the gatewayclass has been accepted by the pixiu-gateway-controller",
 		LastTransitionTime: meta.Now(),
 	}
 
diff --git a/controllers/internal/controller/gatewayproxy_controller.go b/controllers/internal/controller/gatewayproxy_controller.go
deleted file mode 100644
index 1917060a..00000000
--- a/controllers/internal/controller/gatewayproxy_controller.go
+++ /dev/null
@@ -1,204 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package controller
-
-import (
-	"context"
-	"errors"
-)
-
-import (
-	"controllers/api/v1alpha1"
-
-	"controllers/internal/controller/config"
-	"controllers/internal/controller/indexer"
-
-	"controllers/internal/utils"
-
-	"github.com/go-logr/logr"
-
-	corev1 "k8s.io/api/core/v1"
-
-	discoveryv1 "k8s.io/api/discovery/v1"
-
-	networkingv1 "k8s.io/api/networking/v1"
-
-	"k8s.io/apimachinery/pkg/runtime"
-
-	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/predicate"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
-
-	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
-)
-
-// GatewayProxyController reconciles a GatewayProxy object.
-type GatewayProxyController struct {
-	client.Client
-	Scheme *runtime.Scheme
-	Log    logr.Logger
-}
-
-func (r *GatewayProxyController) SetupWithManager(mrg ctrl.Manager) error {
-	return ctrl.NewControllerManagedBy(mrg).
-		For(&v1alpha1.GatewayProxy{}).
-		WithEventFilter(
-			predicate.Or(
-				predicate.GenerationChangedPredicate{},
-				predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
-			),
-		).
-		Watches(&corev1.Service{},
-			handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderService),
-		).
-		Watches(&discoveryv1.EndpointSlice{},
-			handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForProviderEndpointSlice),
-		).
-		Watches(&corev1.Secret{},
-			handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForSecret),
-		).
-		Watches(&gatewayv1.Gateway{},
-			handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesByGateway),
-		).
-		Watches(&networkingv1.IngressClass{},
-			handler.EnqueueRequestsFromMapFunc(r.listGatewayProxiesForIngressClass),
-		).
-		Complete(r)
-}
-
-func (r *GatewayProxyController) Reconcile(ctx context.Context, req ctrl.Request) (reconcile.Result, error) {
-	var gp v1alpha1.GatewayProxy
-	if err := r.Get(ctx, req.NamespacedName, &gp); err != nil {
-		if client.IgnoreNotFound(err) == nil {
-			gp.Namespace = req.Namespace
-			gp.Name = req.Name
-		}
-		return ctrl.Result{}, err
-	}
-
-	// list Gateways that reference the GatewayProxy
-	var (
-		gatewayList      gatewayv1.GatewayList
-		ingressClassList networkingv1.IngressClassList
-		indexKey         = indexer.GenIndexKey(gp.GetNamespace(), gp.GetName())
-	)
-	if err := r.List(ctx, &gatewayList, client.MatchingFields{indexer.ParametersRef: indexKey}); err != nil {
-		r.Log.Error(err, "failed to list GatewayList")
-		return ctrl.Result{}, nil
-	}
-
-	var gatewayclassList gatewayv1.GatewayClassList
-	if err := r.List(ctx, &gatewayclassList, client.MatchingFields{indexer.ControllerName: config.GetControllerName()}); err != nil {
-		r.Log.Error(err, "failed to list GatewayClassList")
-		return ctrl.Result{}, nil
-	}
-	gcMatched := make(map[string]*gatewayv1.GatewayClass)
-	for _, item := range gatewayclassList.Items {
-		gcMatched[item.Name] = &item
-	}
-
-	// list IngressClasses that reference the GatewayProxy
-	if err := r.List(ctx, &ingressClassList, client.MatchingFields{indexer.IngressClassParametersRef: indexKey}); err != nil {
-		r.Log.Error(err, "failed to list IngressClassList")
-		return reconcile.Result{}, err
-	}
-
-	// append referrers to translate context
-	for _, item := range gatewayList.Items {
-		gcName := string(item.Spec.GatewayClassName)
-		if gcName == "" {
-			continue
-		}
-	}
-	for _, item := range ingressClassList.Items {
-		if item.Spec.Controller != config.GetControllerName() {
-			continue
-		}
-	}
-
-	return reconcile.Result{}, nil
-}
-
-func (r *GatewayProxyController) listGatewayProxiesForProviderService(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
-	service, ok := obj.(*corev1.Service)
-	if !ok {
-		r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Service")
-		return nil
-	}
-
-	return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
-		indexer.ServiceIndexRef: indexer.GenIndexKey(service.GetNamespace(), service.GetName()),
-	})
-}
-
-func (r *GatewayProxyController) listGatewayProxiesForProviderEndpointSlice(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
-	endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
-	if !ok {
-		r.Log.Error(errors.New("unexpected object type"), "failed to convert object to EndpointSlice")
-		return nil
-	}
-
-	return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
-		indexer.ServiceIndexRef: indexer.GenIndexKey(endpointSlice.GetNamespace(), endpointSlice.Labels[discoveryv1.LabelServiceName]),
-	})
-}
-
-func (r *GatewayProxyController) listGatewayProxiesForSecret(ctx context.Context, object client.Object) []reconcile.Request {
-	secret, ok := object.(*corev1.Secret)
-	if !ok {
-		r.Log.Error(errors.New("unexpected object type"), "failed to convert object to Secret")
-		return nil
-	}
-	return ListRequests(ctx, r.Client, r.Log, &v1alpha1.GatewayProxyList{}, client.MatchingFields{
-		indexer.SecretIndexRef: indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()),
-	})
-}
-
-func (r *GatewayProxyController) listGatewayProxiesForIngressClass(ctx context.Context, object client.Object) []reconcile.Request {
-	ingressClass, ok := object.(*networkingv1.IngressClass)
-	if !ok {
-		r.Log.Error(errors.New("unexpected object type"), "failed to convert object to IngressClass")
-		return nil
-	}
-	reqs := []reconcile.Request{}
-	gp, _ := GetGatewayProxyByIngressClass(ctx, r.Client, ingressClass)
-	if gp != nil {
-		reqs = append(reqs, reconcile.Request{
-			NamespacedName: utils.NamespacedName(gp),
-		})
-	}
-	return reqs
-}
-
-func (r *GatewayProxyController) listGatewayProxiesByGateway(ctx context.Context, object client.Object) []reconcile.Request {
-	gateway, ok := object.(*gatewayv1.Gateway)
-	if !ok {
-		r.Log.Error(errors.New("unexpected object type"), "failed to convert object to IngressClass")
-		return nil
-	}
-	reqs := []reconcile.Request{}
-	gp, _ := GetGatewayProxyByGateway(ctx, r.Client, gateway)
-	if gp != nil {
-		reqs = append(reqs, reconcile.Request{
-			NamespacedName: utils.NamespacedName(gp),
-		})
-	}
-	return reqs
-}
diff --git a/controllers/internal/controller/indexer/indexer.go b/controllers/internal/controller/indexer/indexer.go
index 145fb85c..11f82725 100644
--- a/controllers/internal/controller/indexer/indexer.go
+++ b/controllers/internal/controller/indexer/indexer.go
@@ -22,10 +22,6 @@ import (
 )
 
 import (
-	"controllers/api/v1alpha1"
-
-	networkingv1 "k8s.io/api/networking/v1"
-
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 
@@ -33,23 +29,17 @@ import (
 )
 
 const (
-	ServiceIndexRef           = "serviceRefs"
-	ParametersRef             = "parametersRef"
-	IngressClass              = "ingressClass"
-	IngressClassRef           = "ingressClassRef"
-	IngressClassParametersRef = "ingressClassParametersRef"
-	SecretIndexRef            = "secretRefs"
-	GatewayClassIndexRef      = "gatewayClassRef"
-	ControllerName            = "controllerName"
+	ServiceIndexRef      = "serviceRefs"
+	ParametersRef        = "parametersRef"
+	SecretIndexRef       = "secretRefs"
+	GatewayClassIndexRef = "gatewayClassRef"
+	ControllerName       = "controllerName"
 )
 
 func SetupIndexer(mgr ctrl.Manager) error {
 	for _, setup := range []func(ctrl.Manager) error{
 		setupGatewayClassIndexer,
 		setupGatewayIndexer,
-		setupIngressIndexer,
-		setupIngressClassIndexer,
-		setupGatewayProxyIndexer,
 	} {
 		if err := setup(mgr); err != nil {
 			return err
@@ -88,85 +78,6 @@ func setupGatewayIndexer(mgr ctrl.Manager) error {
 	return nil
 }
 
-func setupIngressIndexer(mgr ctrl.Manager) error {
-	// create IngressClass index
-	if err := mgr.GetFieldIndexer().IndexField(
-		context.Background(),
-		&networkingv1.Ingress{},
-		IngressClassRef,
-		IngressClassRefIndexFunc,
-	); err != nil {
-		return err
-	}
-
-	// create Service index for quick lookup of Ingresses using specific services
-	if err := mgr.GetFieldIndexer().IndexField(
-		context.Background(),
-		&networkingv1.Ingress{},
-		ServiceIndexRef,
-		IngressServiceIndexFunc,
-	); err != nil {
-		return err
-	}
-
-	// create secret index for TLS
-	if err := mgr.GetFieldIndexer().IndexField(
-		context.Background(),
-		&networkingv1.Ingress{},
-		SecretIndexRef,
-		IngressSecretIndexFunc,
-	); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func setupIngressClassIndexer(mgr ctrl.Manager) error {
-	// create IngressClass index
-	if err := mgr.GetFieldIndexer().IndexField(
-		context.Background(),
-		&networkingv1.IngressClass{},
-		IngressClass,
-		IngressClassIndexFunc,
-	); err != nil {
-		return err
-	}
-
-	// create IngressClassParametersRef index
-	if err := mgr.GetFieldIndexer().IndexField(
-		context.Background(),
-		&networkingv1.IngressClass{},
-		IngressClassParametersRef,
-		IngressClassParametersRefIndexFunc,
-	); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-func setupGatewayProxyIndexer(mgr ctrl.Manager) error {
-	if err := mgr.GetFieldIndexer().IndexField(
-		context.Background(),
-		&v1alpha1.GatewayProxy{},
-		ServiceIndexRef,
-		GatewayProxyServiceIndexFunc,
-	); err != nil {
-		return err
-	}
-
-	if err := mgr.GetFieldIndexer().IndexField(
-		context.Background(),
-		&v1alpha1.GatewayProxy{},
-		SecretIndexRef,
-		GatewayProxySecretIndexFunc,
-	); err != nil {
-		return err
-	}
-	return nil
-}
-
 func setupGatewayClassIndexer(mgr ctrl.Manager) error {
 	return mgr.GetFieldIndexer().IndexField(
 		context.Background(),
@@ -178,91 +89,7 @@ func setupGatewayClassIndexer(mgr ctrl.Manager) error {
 	)
 }
 
-func GatewayProxyServiceIndexFunc(rawObj client.Object) []string {
-	return nil
-}
-
-func GatewayProxySecretIndexFunc(rawObj client.Object) []string {
-	secretKeys := make([]string, 0)
-	return secretKeys
-}
-
 func GatewayParametersRefIndexFunc(rawObj client.Object) []string {
-	gw := rawObj.(*gatewayv1.Gateway)
-	if gw.Spec.Infrastructure != nil && gw.Spec.Infrastructure.ParametersRef != nil {
-		// now we only care about kind: GatewayProxy
-		if gw.Spec.Infrastructure.ParametersRef.Kind == "GatewayProxy" {
-			name := gw.Spec.Infrastructure.ParametersRef.Name
-			return []string{GenIndexKey(gw.GetNamespace(), name)}
-		}
-	}
+	// Infrastructure parameters are no longer used
 	return nil
 }
-
-func IngressClassIndexFunc(rawObj client.Object) []string {
-	ingressClass := rawObj.(*networkingv1.IngressClass)
-	if ingressClass.Spec.Controller == "" {
-		return nil
-	}
-	controllerName := ingressClass.Spec.Controller
-	return []string{controllerName}
-}
-
-func IngressClassRefIndexFunc(rawObj client.Object) []string {
-	ingress := rawObj.(*networkingv1.Ingress)
-	if ingress.Spec.IngressClassName == nil {
-		return nil
-	}
-	return []string{*ingress.Spec.IngressClassName}
-}
-
-func IngressClassParametersRefIndexFunc(rawObj client.Object) []string {
-	ingressClass := rawObj.(*networkingv1.IngressClass)
-	// check if the IngressClass references this gateway proxy
-	if ingressClass.Spec.Parameters != nil &&
-		ingressClass.Spec.Parameters.APIGroup != nil &&
-		*ingressClass.Spec.Parameters.APIGroup == v1alpha1.GroupVersion.Group &&
-		ingressClass.Spec.Parameters.Kind == "GatewayProxy" {
-		ns := ingressClass.GetNamespace()
-		if ingressClass.Spec.Parameters.Namespace != nil {
-			ns = *ingressClass.Spec.Parameters.Namespace
-		}
-		return []string{GenIndexKey(ns, ingressClass.Spec.Parameters.Name)}
-	}
-	return nil
-}
-
-func IngressServiceIndexFunc(rawObj client.Object) []string {
-	ingress := rawObj.(*networkingv1.Ingress)
-	var services []string
-
-	for _, rule := range ingress.Spec.Rules {
-		if rule.HTTP == nil {
-			continue
-		}
-
-		for _, path := range rule.HTTP.Paths {
-			if path.Backend.Service == nil {
-				continue
-			}
-			key := GenIndexKey(ingress.Namespace, path.Backend.Service.Name)
-			services = append(services, key)
-		}
-	}
-	return services
-}
-
-func IngressSecretIndexFunc(rawObj client.Object) []string {
-	ingress := rawObj.(*networkingv1.Ingress)
-	secrets := make([]string, 0)
-
-	for _, tls := range ingress.Spec.TLS {
-		if tls.SecretName == "" {
-			continue
-		}
-
-		key := GenIndexKey(ingress.Namespace, tls.SecretName)
-		secrets = append(secrets, key)
-	}
-	return secrets
-}
diff --git a/controllers/internal/controller/ingress_controller.go b/controllers/internal/controller/ingress_controller.go
deleted file mode 100644
index aea15c0a..00000000
--- a/controllers/internal/controller/ingress_controller.go
+++ /dev/null
@@ -1,519 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package controller
-
-import (
-	"context"
-	"fmt"
-	"reflect"
-)
-
-import (
-	"controllers/api/v1alpha1"
-
-	"controllers/internal/controller/indexer"
-	"controllers/internal/controller/status"
-
-	"controllers/internal/utils"
-
-	"github.com/go-logr/logr"
-
-	corev1 "k8s.io/api/core/v1"
-
-	discoveryv1 "k8s.io/api/discovery/v1"
-
-	networkingv1 "k8s.io/api/networking/v1"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/types"
-
-	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/builder"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/event"
-	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/predicate"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
-	"sigs.k8s.io/controller-runtime/pkg/source"
-)
-
-const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
-
-// IngressReconciler reconciles a Ingress object.
-type IngressReconciler struct { //nolint:revive
-	client.Client
-	Scheme       *runtime.Scheme
-	Log          logr.Logger
-	genericEvent chan event.GenericEvent
-	Updater      status.Updater
-}
-
-// SetupWithManager sets up the controller with the Manager.
-func (r *IngressReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	r.genericEvent = make(chan event.GenericEvent, 100)
-
-	return ctrl.NewControllerManagedBy(mgr).
-		For(&networkingv1.Ingress{},
-			builder.WithPredicates(
-				predicate.NewPredicateFuncs(r.checkIngressClass),
-			),
-		).
-		WithEventFilter(
-			predicate.Or(
-				predicate.GenerationChangedPredicate{},
-				predicate.AnnotationChangedPredicate{},
-				predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
-			),
-		).
-		Watches(
-			&networkingv1.IngressClass{},
-			handler.EnqueueRequestsFromMapFunc(r.listIngressForIngressClass),
-			builder.WithPredicates(
-				predicate.NewPredicateFuncs(r.matchesIngressController),
-			),
-		).
-		Watches(
-			&discoveryv1.EndpointSlice{},
-			handler.EnqueueRequestsFromMapFunc(r.listIngressesByService),
-		).
-		Watches(
-			&corev1.Secret{},
-			handler.EnqueueRequestsFromMapFunc(r.listIngressesBySecret),
-		).
-		Watches(&v1alpha1.GatewayProxy{},
-			handler.EnqueueRequestsFromMapFunc(r.listIngressesForGatewayProxy),
-		).
-		WatchesRawSource(
-			source.Channel(
-				r.genericEvent,
-				handler.EnqueueRequestsFromMapFunc(r.listIngressForGenericEvent),
-			),
-		).
-		Complete(r)
-}
-
-// Reconcile handles the reconciliation of Ingress resources
-func (r *IngressReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-	ingress := new(networkingv1.Ingress)
-	if err := r.Get(ctx, req.NamespacedName, ingress); err != nil {
-		if client.IgnoreNotFound(err) == nil {
-			ingress.Namespace = req.Namespace
-			ingress.Name = req.Name
-
-			ingress.TypeMeta = metav1.TypeMeta{
-				Kind:       KindIngress,
-				APIVersion: networkingv1.SchemeGroupVersion.String(),
-			}
-			r.Log.Info("deleted ingress resources", "ingress", ingress.Name)
-			return ctrl.Result{}, nil
-		}
-		return ctrl.Result{}, err
-	}
-
-	r.Log.Info("reconciling ingress", "ingress", ingress.Name)
-
-	ingressClass, err := r.getIngressClass(ctx, ingress)
-	if err != nil {
-		r.Log.Error(err, "failed to get IngressClass")
-		return ctrl.Result{}, err
-	}
-
-	// process IngressClass parameters if they reference GatewayProxy
-	if err := ProcessIngressClassParameters(r.Client, r.Log, ingress, ingressClass); err != nil {
-		r.Log.Error(err, "failed to process IngressClass parameters", "ingressClass", ingressClass.Name)
-		return ctrl.Result{}, err
-	}
-
-	// process backend services
-	if err := r.processBackends(ingress); err != nil {
-		r.Log.Error(err, "failed to process backend services", "ingress", ingress.Name)
-		return ctrl.Result{}, err
-	}
-
-	// update the ingress status
-	if err := r.updateStatus(ctx, ingress, ingressClass); err != nil {
-		r.Log.Error(err, "failed to update ingress status", "ingress", ingress.Name)
-		return ctrl.Result{}, err
-	}
-
-	return ctrl.Result{}, nil
-}
-
-// getIngressClass get the ingress class for the ingress
-func (r *IngressReconciler) getIngressClass(ctx context.Context, obj client.Object) (*networkingv1.IngressClass, error) {
-	ingress := obj.(*networkingv1.Ingress)
-	var ingressClassName string
-	if ingress.Spec.IngressClassName != nil {
-		ingressClassName = *ingress.Spec.IngressClassName
-	}
-	return GetIngressClass(ctx, r.Client, r.Log, ingressClassName)
-}
-
-// checkIngressClass check if the ingress uses the ingress class that we control
-func (r *IngressReconciler) checkIngressClass(obj client.Object) bool {
-	_, err := r.getIngressClass(context.Background(), obj)
-	return err == nil
-}
-
-// matchesIngressController check if the ingress class is controlled by us
-func (r *IngressReconciler) matchesIngressController(obj client.Object) bool {
-	ingressClass, ok := obj.(*networkingv1.IngressClass)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClass")
-		return false
-	}
-
-	return matchesController(ingressClass.Spec.Controller)
-}
-
-// listIngressForIngressClass list all ingresses that use a specific ingress class
-func (r *IngressReconciler) listIngressForIngressClass(ctx context.Context, obj client.Object) []reconcile.Request {
-	ingressClass, ok := obj.(*networkingv1.IngressClass)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClass")
-		return nil
-	}
-
-	// check if the ingress class is the default ingress class
-	if IsDefaultIngressClass(ingressClass) {
-		ingressList := &networkingv1.IngressList{}
-		if err := r.List(ctx, ingressList); err != nil {
-			r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName())
-			return nil
-		}
-
-		requests := make([]reconcile.Request, 0, len(ingressList.Items))
-		for _, ingress := range ingressList.Items {
-			if ingress.Spec.IngressClassName == nil || *ingress.Spec.IngressClassName == "" ||
-				*ingress.Spec.IngressClassName == ingressClass.GetName() {
-				requests = append(requests, reconcile.Request{
-					NamespacedName: client.ObjectKey{
-						Namespace: ingress.Namespace,
-						Name:      ingress.Name,
-					},
-				})
-			}
-		}
-		return requests
-	} else {
-		ingressList := &networkingv1.IngressList{}
-		if err := r.List(ctx, ingressList, client.MatchingFields{
-			indexer.IngressClassRef: ingressClass.GetName(),
-		}); err != nil {
-			r.Log.Error(err, "failed to list ingresses for ingress class", "ingressclass", ingressClass.GetName())
-			return nil
-		}
-
-		requests := make([]reconcile.Request, 0, len(ingressList.Items))
-		for _, ingress := range ingressList.Items {
-			requests = append(requests, reconcile.Request{
-				NamespacedName: client.ObjectKey{
-					Namespace: ingress.Namespace,
-					Name:      ingress.Name,
-				},
-			})
-		}
-
-		return requests
-	}
-}
-
-func IsDefaultIngressClass(obj client.Object) bool {
-	if ingressClass, ok := obj.(*networkingv1.IngressClass); ok {
-		return ingressClass.Annotations[defaultIngressClassAnnotation] == "true"
-	}
-	return false
-}
-
-// listIngressesByService list all ingresses that use a specific service
-func (r *IngressReconciler) listIngressesByService(ctx context.Context, obj client.Object) []reconcile.Request {
-	endpointSlice, ok := obj.(*discoveryv1.EndpointSlice)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to EndpointSlice")
-		return nil
-	}
-
-	namespace := endpointSlice.GetNamespace()
-	serviceName := endpointSlice.Labels[discoveryv1.LabelServiceName]
-
-	ingressList := &networkingv1.IngressList{}
-	if err := r.List(ctx, ingressList, client.MatchingFields{
-		indexer.ServiceIndexRef: indexer.GenIndexKey(namespace, serviceName),
-	}); err != nil {
-		r.Log.Error(err, "failed to list ingresses by service", "service", serviceName)
-		return nil
-	}
-
-	requests := make([]reconcile.Request, 0, len(ingressList.Items))
-	for _, ingress := range ingressList.Items {
-		if r.checkIngressClass(&ingress) {
-			requests = append(requests, reconcile.Request{
-				NamespacedName: client.ObjectKey{
-					Namespace: ingress.Namespace,
-					Name:      ingress.Name,
-				},
-			})
-		}
-	}
-	return requests
-}
-
-// listIngressesBySecret list all ingresses that use a specific secret
-func (r *IngressReconciler) listIngressesBySecret(ctx context.Context, obj client.Object) []reconcile.Request {
-	secret, ok := obj.(*corev1.Secret)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Secret")
-		return nil
-	}
-
-	namespace := secret.GetNamespace()
-	name := secret.GetName()
-
-	ingressList := &networkingv1.IngressList{}
-	if err := r.List(ctx, ingressList, client.MatchingFields{
-		indexer.SecretIndexRef: indexer.GenIndexKey(namespace, name),
-	}); err != nil {
-		r.Log.Error(err, "failed to list ingresses by secret", "secret", name)
-		return nil
-	}
-
-	requests := make([]reconcile.Request, 0, len(ingressList.Items))
-	for _, ingress := range ingressList.Items {
-		if r.checkIngressClass(&ingress) {
-			requests = append(requests, reconcile.Request{
-				NamespacedName: client.ObjectKey{
-					Namespace: ingress.Namespace,
-					Name:      ingress.Name,
-				},
-			})
-		}
-	}
-
-	gatewayProxyList := &v1alpha1.GatewayProxyList{}
-	if err := r.List(ctx, gatewayProxyList, client.MatchingFields{
-		indexer.SecretIndexRef: indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()),
-	}); err != nil {
-		r.Log.Error(err, "failed to list gateway proxies by secret", "secret", secret.GetName())
-		return nil
-	}
-
-	for _, gatewayProxy := range gatewayProxyList.Items {
-		var (
-			ingressClassList networkingv1.IngressClassList
-			indexKey         = indexer.GenIndexKey(gatewayProxy.GetNamespace(), gatewayProxy.GetName())
-			matchingFields   = client.MatchingFields{indexer.IngressClassParametersRef: indexKey}
-		)
-		if err := r.List(ctx, &ingressClassList, matchingFields); err != nil {
-			r.Log.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", indexKey)
-			continue
-		}
-		for _, ingressClass := range ingressClassList.Items {
-			requests = append(requests, r.listIngressForIngressClass(ctx, &ingressClass)...)
-		}
-	}
-
-	return distinctRequests(requests)
-}
-
-func (r *IngressReconciler) listIngressForGenericEvent(ctx context.Context, obj client.Object) (requests []reconcile.Request) {
-	switch obj.(type) {
-	default:
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to BackendTrafficPolicy")
-		return nil
-	}
-}
-
-// processTLS process the TLS configuration of the ingress
-func (r *IngressReconciler) processTLS(ingress *networkingv1.Ingress) error {
-	for _, tls := range ingress.Spec.TLS {
-		if tls.SecretName == "" {
-			continue
-		}
-
-		secret := corev1.Secret{}
-		if err := r.Get(context.TODO(), client.ObjectKey{
-			Namespace: ingress.Namespace,
-			Name:      tls.SecretName,
-		}, &secret); err != nil {
-			r.Log.Error(err, "failed to get secret", "namespace", ingress.Namespace, "name", tls.SecretName)
-			return err
-		}
-
-		if secret.Data == nil {
-			continue
-		}
-	}
-
-	return nil
-}
-
-// processBackends process the backend services of the ingress
-func (r *IngressReconciler) processBackends(ingress *networkingv1.Ingress) error {
-	var terr error
-
-	// process all the backend services in the rules
-	for _, rule := range ingress.Spec.Rules {
-		if rule.HTTP == nil {
-			continue
-		}
-		for _, path := range rule.HTTP.Paths {
-			if path.Backend.Service == nil {
-				continue
-			}
-			service := path.Backend.Service
-			if err := r.processBackendService(ingress.Namespace, service); err != nil {
-				terr = err
-			}
-		}
-	}
-	return terr
-}
-
-// processBackendService process a single backend service
-func (r *IngressReconciler) processBackendService(namespace string, backendService *networkingv1.IngressServiceBackend) error {
-	// get the service
-	var service corev1.Service
-	serviceNS := types.NamespacedName{
-		Namespace: namespace,
-		Name:      backendService.Name,
-	}
-	if err := r.Get(context.TODO(), serviceNS, &service); err != nil {
-		if client.IgnoreNotFound(err) == nil {
-			r.Log.Info("service not found", "namespace", namespace, "name", backendService.Name)
-			return nil
-		}
-		return err
-	}
-
-	if service.Spec.Type == corev1.ServiceTypeExternalName {
-		return nil
-	}
-
-	// verify if the port exists
-	var portExists bool
-	if backendService.Port.Number != 0 {
-		for _, servicePort := range service.Spec.Ports {
-			if servicePort.Port == backendService.Port.Number {
-				portExists = true
-				break
-			}
-		}
-	} else if backendService.Port.Name != "" {
-		for _, servicePort := range service.Spec.Ports {
-			if servicePort.Name == backendService.Port.Name {
-				portExists = true
-				break
-			}
-		}
-	}
-
-	if !portExists {
-		err := fmt.Errorf("port(name: %s, number: %d) not found in service %s/%s", backendService.Port.Name, backendService.Port.Number, namespace, backendService.Name)
-		r.Log.Error(err, "service port not found")
-		return err
-	}
-
-	// get the endpoint slices
-	endpointSliceList := &discoveryv1.EndpointSliceList{}
-	if err := r.List(context.TODO(), endpointSliceList,
-		client.InNamespace(namespace),
-		client.MatchingLabels{
-			discoveryv1.LabelServiceName: backendService.Name,
-		},
-	); err != nil {
-		r.Log.Error(err, "failed to list endpoint slices", "namespace", namespace, "name", backendService.Name)
-		return err
-	}
-
-	return nil
-}
-
-// updateStatus update the status of the ingress
-func (r *IngressReconciler) updateStatus(ctx context.Context, ingress *networkingv1.Ingress, ingressClass *networkingv1.IngressClass) error {
-	var loadBalancerStatus networkingv1.IngressLoadBalancerStatus
-
-	// update the load balancer status
-	if len(loadBalancerStatus.Ingress) > 0 && !reflect.DeepEqual(ingress.Status.LoadBalancer, loadBalancerStatus) {
-		ingress.Status.LoadBalancer = loadBalancerStatus
-		r.Updater.Update(status.Update{
-			NamespacedName: utils.NamespacedName(ingress),
-			Resource:       ingress.DeepCopy(),
-			Mutator: status.MutatorFunc(func(obj client.Object) client.Object {
-				cp := obj.(*networkingv1.Ingress).DeepCopy()
-				cp.Status = ingress.Status
-				return cp
-			}),
-		})
-		return nil
-	}
-
-	return nil
-}
-
-// listIngressesForGatewayProxy list all ingresses that use a specific gateway proxy
-func (r *IngressReconciler) listIngressesForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request {
-	return listIngressClassRequestsForGatewayProxy(ctx, r.Client, obj, r.Log, r.listIngressForIngressClass)
-}
-
-func listIngressClassRequestsForGatewayProxy(
-	ctx context.Context,
-	c client.Client,
-	obj client.Object,
-	logger logr.Logger,
-	listFunc func(context.Context, client.Object) []reconcile.Request,
-) []reconcile.Request {
-	gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy)
-	if !ok {
-		return nil
-	}
-
-	ingressClassList := &networkingv1.IngressClassList{}
-	if err := c.List(ctx, ingressClassList, client.MatchingFields{
-		indexer.IngressClassParametersRef: indexer.GenIndexKey(gatewayProxy.GetNamespace(), gatewayProxy.GetName()),
-	}); err != nil {
-		logger.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", gatewayProxy.GetName())
-		return nil
-	}
-
-	requestSet := make(map[string]reconcile.Request)
-	for _, ingressClass := range ingressClassList.Items {
-		for _, req := range listFunc(ctx, &ingressClass) {
-			requestSet[req.String()] = req
-		}
-	}
-
-	requests := make([]reconcile.Request, 0, len(requestSet))
-	for _, req := range requestSet {
-		requests = append(requests, req)
-	}
-	return requests
-}
-
-func distinctRequests(requests []reconcile.Request) []reconcile.Request {
-	uniqueRequests := make(map[string]reconcile.Request)
-	for _, request := range requests {
-		uniqueRequests[request.String()] = request
-	}
-
-	distinctRequests := make([]reconcile.Request, 0, len(uniqueRequests))
-	for _, request := range uniqueRequests {
-		distinctRequests = append(distinctRequests, request)
-	}
-	return distinctRequests
-}
diff --git a/controllers/internal/controller/ingressclass_controller.go b/controllers/internal/controller/ingressclass_controller.go
deleted file mode 100644
index 0192d38f..00000000
--- a/controllers/internal/controller/ingressclass_controller.go
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package controller
-
-import (
-	"context"
-	"fmt"
-)
-
-import (
-	"controllers/api/v1alpha1"
-
-	"controllers/internal/controller/indexer"
-
-	"github.com/go-logr/logr"
-
-	corev1 "k8s.io/api/core/v1"
-
-	networkingv1 "k8s.io/api/networking/v1"
-
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-
-	ctrl "sigs.k8s.io/controller-runtime"
-	"sigs.k8s.io/controller-runtime/pkg/builder"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-	"sigs.k8s.io/controller-runtime/pkg/handler"
-	"sigs.k8s.io/controller-runtime/pkg/predicate"
-	"sigs.k8s.io/controller-runtime/pkg/reconcile"
-)
-
-// IngressClassReconciler reconciles a IngressClass object.
-type IngressClassReconciler struct {
-	client.Client
-	Scheme *runtime.Scheme
-	Log    logr.Logger
-}
-
-// SetupWithManager sets up the controller with the Manager.
-func (r *IngressClassReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	return ctrl.NewControllerManagedBy(mgr).
-		For(
-			&networkingv1.IngressClass{},
-			builder.WithPredicates(
-				predicate.NewPredicateFuncs(r.matchesController),
-			),
-		).
-		WithEventFilter(
-			predicate.Or(
-				predicate.GenerationChangedPredicate{},
-				predicate.AnnotationChangedPredicate{},
-				predicate.NewPredicateFuncs(TypePredicate[*corev1.Secret]()),
-			),
-		).
-		Watches(
-			&v1alpha1.GatewayProxy{},
-			handler.EnqueueRequestsFromMapFunc(r.listIngressClassesForGatewayProxy),
-		).
-		Watches(
-			&corev1.Secret{},
-			handler.EnqueueRequestsFromMapFunc(r.listIngressClassesForSecret),
-		).
-		Complete(r)
-}
-
-func (r *IngressClassReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
-	ingressClass := new(networkingv1.IngressClass)
-	if err := r.Get(ctx, req.NamespacedName, ingressClass); err != nil {
-		if client.IgnoreNotFound(err) == nil {
-			ingressClass.Name = req.Name
-
-			ingressClass.TypeMeta = metav1.TypeMeta{
-				Kind:       KindIngressClass,
-				APIVersion: networkingv1.SchemeGroupVersion.String(),
-			}
-
-			return ctrl.Result{}, nil
-		}
-		return ctrl.Result{}, err
-	}
-
-	if err := r.processInfrastructure(ingressClass); err != nil {
-		r.Log.Error(err, "failed to process infrastructure for ingressclass", "ingressclass", ingressClass.GetName())
-		return ctrl.Result{}, client.IgnoreNotFound(err)
-	}
-
-	return ctrl.Result{}, nil
-}
-
-func (r *IngressClassReconciler) matchesController(obj client.Object) bool {
-	ingressClass, ok := obj.(*networkingv1.IngressClass)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to IngressClass")
-		return false
-	}
-	return matchesController(ingressClass.Spec.Controller)
-}
-
-func (r *IngressClassReconciler) listIngressClassesForGatewayProxy(ctx context.Context, obj client.Object) []reconcile.Request {
-	gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to GatewayProxy")
-		return nil
-	}
-	namespace := gatewayProxy.GetNamespace()
-	name := gatewayProxy.GetName()
-
-	ingressClassList := &networkingv1.IngressClassList{}
-	if err := r.List(ctx, ingressClassList, client.MatchingFields{
-		indexer.IngressClassParametersRef: indexer.GenIndexKey(namespace, name),
-	}); err != nil {
-		r.Log.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", gatewayProxy.GetName())
-		return nil
-	}
-
-	recs := make([]reconcile.Request, 0, len(ingressClassList.Items))
-	for _, ingressClass := range ingressClassList.Items {
-		if !r.matchesController(&ingressClass) {
-			continue
-		}
-		recs = append(recs, reconcile.Request{
-			NamespacedName: client.ObjectKey{
-				Name: ingressClass.GetName(),
-			},
-		})
-	}
-	return recs
-}
-
-func (r *IngressClassReconciler) listIngressClassesForSecret(ctx context.Context, obj client.Object) []reconcile.Request {
-	secret, ok := obj.(*corev1.Secret)
-	if !ok {
-		r.Log.Error(fmt.Errorf("unexpected object type"), "failed to convert object to Secret")
-		return nil
-	}
-
-	// 1. list gateway proxies by secret
-	gatewayProxyList := &v1alpha1.GatewayProxyList{}
-	if err := r.List(ctx, gatewayProxyList, client.MatchingFields{
-		indexer.SecretIndexRef: indexer.GenIndexKey(secret.GetNamespace(), secret.GetName()),
-	}); err != nil {
-		r.Log.Error(err, "failed to list gateway proxies by secret", "secret", secret.GetName())
-		return nil
-	}
-
-	// 2. list ingress classes by gateway proxies
-	requests := make([]reconcile.Request, 0)
-	for _, gatewayProxy := range gatewayProxyList.Items {
-		requests = append(requests, r.listIngressClassesForGatewayProxy(ctx, &gatewayProxy)...)
-	}
-
-	return distinctRequests(requests)
-}
-
-func (r *IngressClassReconciler) processInfrastructure(ingressClass *networkingv1.IngressClass) error {
-	if ingressClass.Spec.Parameters == nil {
-		return nil
-	}
-
-	if ingressClass.Spec.Parameters.APIGroup == nil ||
-		*ingressClass.Spec.Parameters.APIGroup != v1alpha1.GroupVersion.Group ||
-		ingressClass.Spec.Parameters.Kind != KindGatewayProxy {
-		return nil
-	}
-
-	namespace := ingressClass.Namespace
-	if ingressClass.Spec.Parameters.Namespace != nil {
-		namespace = *ingressClass.Spec.Parameters.Namespace
-	}
-
-	gatewayProxy := new(v1alpha1.GatewayProxy)
-	if err := r.Get(context.Background(), client.ObjectKey{
-		Namespace: namespace,
-		Name:      ingressClass.Spec.Parameters.Name,
-	}, gatewayProxy); err != nil {
-		return fmt.Errorf("failed to get gateway proxy: %w", err)
-	}
-
-	return nil
-}
diff --git a/controllers/internal/controller/utils.go b/controllers/internal/controller/utils.go
index b71caa82..33da4e0c 100644
--- a/controllers/internal/controller/utils.go
+++ b/controllers/internal/controller/utils.go
@@ -19,7 +19,6 @@ package controller
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"path"
 	"reflect"
@@ -27,13 +26,6 @@ import (
 )
 
 import (
-	"controllers/api/v1alpha1"
-
-	"controllers/internal/controller/config"
-	"controllers/internal/controller/indexer"
-
-	"controllers/internal/types"
-
 	"controllers/internal/utils"
 
 	"github.com/go-logr/logr"
@@ -42,8 +34,6 @@ import (
 
 	corev1 "k8s.io/api/core/v1"
 
-	networkingv1 "k8s.io/api/networking/v1"
-
 	"k8s.io/apimachinery/pkg/api/meta"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
@@ -58,12 +48,8 @@ import (
 )
 
 const (
-	KindGateway      = "Gateway"
-	KindGatewayClass = "GatewayClass"
-	KindIngress      = "Ingress"
-	KindIngressClass = "IngressClass"
-	KindGatewayProxy = "GatewayProxy"
-	KindSecret       = "Secret"
+	KindGateway = "Gateway"
+	KindSecret  = "Secret"
 )
 
 var (
@@ -129,115 +115,6 @@ func ListMatchingRequests(ctx context.Context, c client.Client, logger logr.Logg
 	return requests
 }
 
-func ProcessIngressClassParameters(c client.Client, log logr.Logger, object client.Object, ingressClass *networkingv1.IngressClass) error {
-	if ingressClass == nil || ingressClass.Spec.Parameters == nil {
-		return nil
-	}
-
-	parameters := ingressClass.Spec.Parameters
-	// check if the parameters reference GatewayProxy
-	if parameters.APIGroup != nil && *parameters.APIGroup == v1alpha1.GroupVersion.Group && parameters.Kind == KindGatewayProxy {
-		ns := object.GetNamespace()
-		if parameters.Namespace != nil {
-			ns = *parameters.Namespace
-		}
-
-		gatewayProxy := &v1alpha1.GatewayProxy{}
-		if err := c.Get(context.TODO(), client.ObjectKey{
-			Namespace: ns,
-			Name:      parameters.Name,
-		}, gatewayProxy); err != nil {
-			log.Error(err, "failed to get GatewayProxy", "namespace", ns, "name", parameters.Name)
-			return err
-		}
-	}
-
-	return nil
-}
-
-func GetIngressClass(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string) (*networkingv1.IngressClass, error) {
-	if ingressClassName == "" {
-		// Check for default ingress class
-		ingressClassList := &networkingv1.IngressClassList{}
-		if err := c.List(ctx, ingressClassList, client.MatchingFields{
-			indexer.IngressClass: config.GetControllerName(),
-		}); err != nil {
-			log.Error(err, "failed to list ingress classes")
-			return nil, err
-		}
-
-		// Find the ingress class that is marked as default
-		for _, ic := range ingressClassList.Items {
-			if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) {
-				return &ic, nil
-			}
-		}
-		return nil, errors.New("no default ingress class found")
-	}
-
-	// Check if the specified ingress class is controlled by us
-	var ingressClass networkingv1.IngressClass
-	if err := c.Get(ctx, client.ObjectKey{Name: ingressClassName}, &ingressClass); err != nil {
-		return nil, err
-	}
-
-	if matchesController(ingressClass.Spec.Controller) {
-		return &ingressClass, nil
-	}
-
-	return nil, errors.New("ingress class is not controlled by use")
-}
-
-func GetGatewayProxyByIngressClass(ctx context.Context, r client.Client, ingressClass *networkingv1.IngressClass) (*v1alpha1.GatewayProxy, error) {
-	if ingressClass.Spec.Parameters == nil {
-		return nil, nil
-	}
-
-	if ingressClass.Spec.Parameters.APIGroup == nil ||
-		*ingressClass.Spec.Parameters.APIGroup != v1alpha1.GroupVersion.Group ||
-		ingressClass.Spec.Parameters.Kind != KindGatewayProxy {
-		return nil, nil
-	}
-
-	namespace := ingressClass.Namespace
-	if ingressClass.Spec.Parameters.Namespace != nil {
-		namespace = *ingressClass.Spec.Parameters.Namespace
-	}
-
-	gatewayProxy := new(v1alpha1.GatewayProxy)
-	if err := r.Get(ctx, client.ObjectKey{
-		Namespace: namespace,
-		Name:      ingressClass.Spec.Parameters.Name,
-	}, gatewayProxy); err != nil {
-		return nil, fmt.Errorf("failed to get gateway proxy: %w", err)
-	}
-	return gatewayProxy, nil
-}
-
-func GetGatewayProxyByGateway(ctx context.Context, r client.Client, gateway *gatewayv1.Gateway) (*v1alpha1.GatewayProxy, error) {
-	if gateway == nil {
-		return nil, nil
-	}
-	infra := gateway.Spec.Infrastructure
-	if infra == nil || infra.ParametersRef == nil {
-		return nil, nil
-	}
-
-	ns := gateway.GetNamespace()
-	paramRef := infra.ParametersRef
-	if string(paramRef.Group) != v1alpha1.GroupVersion.Group || string(paramRef.Kind) != KindGatewayProxy {
-		return nil, nil
-	}
-	gatewayProxy := &v1alpha1.GatewayProxy{}
-	if err := r.Get(context.Background(), client.ObjectKey{
-		Namespace: ns,
-		Name:      paramRef.Name,
-	}, gatewayProxy); err != nil {
-		return nil, fmt.Errorf("failed to get GatewayProxy: %w", err)
-	}
-	return gatewayProxy, nil
-}
-
 func getListenerStatus(ctx context.Context, mrgc client.Client, gateway *gatewayv1.Gateway) ([]gatewayv1.ListenerStatus, error) {
 	statuses := make(map[gatewayv1.SectionName]gatewayv1.ListenerStatus, len(gateway.Spec.Listeners))
 
@@ -399,7 +276,7 @@ func setGatewayCondition(gw *gatewayv1.Gateway, newCondition metav1.Condition) {
 }
 
 func acceptedMessage(kind string) string {
-	return fmt.Sprintf("the %s has been accepted by the apisix-ingress-controller", kind)
+	return fmt.Sprintf("the %s has been accepted by the pixiu-gateway-controller", kind)
 }
 
 func referenceGrantPredicates(kind gatewayv1.Kind) predicate.Funcs {
@@ -570,31 +447,6 @@ func listenerHostnameIntersectWithRouteHostnames(listener gatewayv1.Listener, ho
 	return false
 }
 
-func ProcessGatewayProxy(r client.Client, log logr.Logger, gateway *gatewayv1.Gateway, rk types.NamespacedNameKind) error {
-	if gateway == nil {
-		return nil
-	}
-	infra := gateway.Spec.Infrastructure
-	if infra == nil || infra.ParametersRef == nil {
-		return nil
-	}
-
-	ns := gateway.GetNamespace()
-	paramRef := infra.ParametersRef
-	if string(paramRef.Group) == v1alpha1.GroupVersion.Group && string(paramRef.Kind) == KindGatewayProxy {
-		gatewayProxy := &v1alpha1.GatewayProxy{}
-		if err := r.Get(context.Background(), client.ObjectKey{
-			Namespace: ns,
-			Name:      paramRef.Name,
-		}, gatewayProxy); err != nil {
-			log.Error(err, "failed to get GatewayProxy", "namespace", ns, "name", paramRef.Name)
-			return err
-		}
-	}
-
-	return nil
-}
-
 func FullTypeName(a any) string {
 	typeOf := reflect.TypeOf(a)
 	pkgPath := typeOf.PkgPath()
diff --git a/controllers/internal/converter/converter.go b/controllers/internal/converter/converter.go
new file mode 100644
index 00000000..60cb201d
--- /dev/null
+++ b/controllers/internal/converter/converter.go
@@ -0,0 +1,275 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package converter
+
+import (
+	"fmt"
+)
+
+import (
+	"controllers/internal/ir"
+
+	"gopkg.in/yaml.v3"
+)
+
+type Converter struct{}
+
+func NewConverter() *Converter {
+	return &Converter{}
+}
+
+func (c *Converter) ConvertIRToPixiuConfig(xds *ir.Xds, clusters []*ir.Cluster) (*PixiuConfig, error) {
+	config := &PixiuConfig{
+		StaticResources: StaticResources{
+			Listeners: []*Listener{},
+			Clusters:  []*Cluster{},
+		},
+		ShutdownConfig: ShutdownConfig{
+			Timeout:      "60s",
+			StepTimeout:  "10s",
+			RejectPolicy: "immediacy",
+		},
+		Log: LogConfig{
+			Level: "info",
+		},
+	}
+
+	for _, httpListener := range xds.HTTP {
+		listener, err := c.convertHTTPListener(httpListener, clusters)
+		if err != nil {
+			return nil, fmt.Errorf("failed to convert HTTP listener %s: %w", httpListener.Name, err)
+		}
+		if listener != nil {
+			config.StaticResources.Listeners = append(config.StaticResources.Listeners, listener)
+		}
+	}
+
+	for _, tcpListener := range xds.TCP {
+		listener, err := c.convertTCPListener(tcpListener, clusters)
+		if err != nil {
+			return nil, fmt.Errorf("failed to convert TCP listener %s: %w", tcpListener.Name, err)
+		}
+		if listener != nil {
+			config.StaticResources.Listeners = append(config.StaticResources.Listeners, listener)
+		}
+	}
+
+	for _, udpListener := range xds.UDP {
+		listener, err := c.convertUDPListener(udpListener)
+		if err != nil {
+			return nil, fmt.Errorf("failed to convert UDP listener %s: %w", udpListener.Name, err)
+		}
+		if listener != nil {
+			config.StaticResources.Listeners = append(config.StaticResources.Listeners, listener)
+		}
+	}
+
+	for _, irCluster := range clusters {
+		cluster := c.convertCluster(irCluster)
+		config.StaticResources.Clusters = append(config.StaticResources.Clusters, cluster)
+	}
+
+	return config, nil
+}
+
+func (c *Converter) convertHTTPListener(httpListener *ir.HTTPListener, clusters []*ir.Cluster) (*Listener, error) {
+	routes := []*Route{}
+	for _, irRoute := range httpListener.Routes {
+		route := c.convertHTTPRoute(irRoute)
+		if route != nil {
+			routes = append(routes, route)
+		}
+	}
+
+	httpFilters := []HTTPFilter{
+		{
+			Name:   "dgp.filter.http.httpproxy",
+			Config: map[string]interface{}{},
+		},
+	}
+
+	for _, irRoute := range httpListener.Routes {
+		for _, filter := range irRoute.Filters {
+			httpFilters = append(httpFilters, HTTPFilter{
+				Name:   filter.Name,
+				Config: filter.Config,
+			})
+		}
+	}
+
+	hcmConfig := HTTPConnectionManagerConfig{
+		RouteConfig: RouteConfiguration{
+			Routes: routes,
+		},
+		HTTPFilters: httpFilters,
+	}
+
+	filterChain := FilterChain{
+		Filters: []NetworkFilter{
+			{
+				Name:   "dgp.filter.httpconnectionmanager",
+				Config: hcmConfig,
+			},
+		},
+	}
+
+	listenerConfig := map[string]interface{}{
+		"idle_timeout":  "5s",
+		"read_timeout":  "5s",
+		"write_timeout": "5s",
+	}
+
+	return &Listener{
+		Name:         httpListener.Name,
+		ProtocolType: "HTTP",
+		Address: Address{
+			SocketAddress: SocketAddress{
+				Address: httpListener.Address,
+				Port:    int(httpListener.Port),
+			},
+		},
+		FilterChain: filterChain,
+		Config:      listenerConfig,
+	}, nil
+}
+
+func (c *Converter) convertHTTPRoute(irRoute *ir.HTTPRoute) *Route {
+	match := RouteMatch{}
+
+	if irRoute.PathMatch != nil {
+		if irRoute.PathMatch.Exact != nil {
+			match.Path = *irRoute.PathMatch.Exact
+		} else if irRoute.PathMatch.Prefix != nil {
+			match.Prefix = *irRoute.PathMatch.Prefix
+		} else if irRoute.PathMatch.SafeRegex != nil {
+			match.Path = *irRoute.PathMatch.SafeRegex
+		}
+	} else {
+		match.Prefix = "/"
+	}
+
+	if irRoute.Method != nil {
+		match.Methods = []string{*irRoute.Method}
+	}
+
+	if len(irRoute.HeaderMatches) > 0 {
+		match.Headers = []HeaderMatcher{}
+		for _, headerMatch := range irRoute.HeaderMatches {
+			header := HeaderMatcher{
+				Name: *headerMatch.Name,
+			}
+			if headerMatch.Exact != nil {
+				header.Values = []string{*headerMatch.Exact}
+			} else if headerMatch.SafeRegex != nil {
+				header.Values = []string{*headerMatch.SafeRegex}
+				header.Regex = true
+			}
+			match.Headers = append(match.Headers, header)
+		}
+	}
+
+	clusterName := ""
+	if len(irRoute.Destinations) > 0 {
+		clusterName = irRoute.Destinations[0].Name
+	}
+
+	routeAction := RouteAction{
+		Cluster:                     clusterName,
+		ClusterNotFoundResponseCode: 503,
+	}
+
+	return &Route{
+		Match: match,
+		Route: routeAction,
+	}
+}
+
+func (c *Converter) convertTCPListener(tcpListener *ir.TCPListener, clusters []*ir.Cluster) (*Listener, error) {
+	// TODO: TCP listener conversion
+	return &Listener{
+		Name:         tcpListener.Name,
+		ProtocolType: "TCP",
+		Address: Address{
+			SocketAddress: SocketAddress{
+				Address: tcpListener.Address,
+				Port:    int(tcpListener.Port),
+			},
+		},
+		FilterChain: FilterChain{
+			Filters: []NetworkFilter{},
+		},
+	}, nil
+}
+
+// convertUDPListener converts an IR UDPListener to a Pixiu Listener.
+func (c *Converter) convertUDPListener(udpListener *ir.UDPListener) (*Listener, error) {
+	// TODO: UDP listener conversion
+	return &Listener{
+		Name:         udpListener.Name,
+		ProtocolType: "UDP",
+		Address: Address{
+			SocketAddress: SocketAddress{
+				Address: udpListener.Address,
+				Port:    int(udpListener.Port),
+			},
+		},
+		FilterChain: FilterChain{
+			Filters: []NetworkFilter{},
+		},
+	}, nil
+}
+
+func (c *Converter) convertCluster(irCluster *ir.Cluster) *Cluster {
+	endpoints := []*Endpoint{}
+	for i, irEndpoint := range irCluster.Endpoints {
+		endpoints = append(endpoints, &Endpoint{
+			ID: i + 1,
+			SocketAddress: SocketAddress{
+				Address: irEndpoint.Address,
+				Port:    int(irEndpoint.Port),
+			},
+		})
+	}
+
+	lbPolicy := "lb" // Default to round_robin
+	if irCluster.LoadBalancerPolicy != nil {
+		switch irCluster.LoadBalancerPolicy.Type {
+		case "round_robin":
+			lbPolicy = "lb"
+		case "least_conn":
+			lbPolicy = "least_conn"
+		default:
+			lbPolicy = "lb"
+		}
+	}
+
+	return &Cluster{
+		Name:      irCluster.Name,
+		Type:      "Static",
+		LbPolicy:  lbPolicy,
+		Endpoints: endpoints,
+	}
+}
+
+func ConvertToYAML(config *PixiuConfig) (string, error) {
+	data, err := yaml.Marshal(config)
+	if err != nil {
+		return "", fmt.Errorf("failed to marshal config to YAML: %w", err)
+	}
+	return string(data), nil
+}
diff --git a/controllers/internal/converter/pixiu_config.go b/controllers/internal/converter/pixiu_config.go
new file mode 100644
index 00000000..4c19b139
--- /dev/null
+++ b/controllers/internal/converter/pixiu_config.go
@@ -0,0 +1,130 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package converter
+
+type PixiuConfig struct {
+	StaticResources  StaticResources   `yaml:"static_resources" json:"static_resources"`
+	DynamicResources *DynamicResources `yaml:"dynamic_resources,omitempty" json:"dynamic_resources,omitempty"`
+	ShutdownConfig   ShutdownConfig    `yaml:"shutdown_config" json:"shutdown_config"`
+	Log              LogConfig         `yaml:"log" json:"log"`
+}
+
+type StaticResources struct {
+	Listeners []*Listener `yaml:"listeners" json:"listeners"`
+	Clusters  []*Cluster  `yaml:"clusters" json:"clusters"`
+}
+
+type Listener struct {
+	Name         string      `yaml:"name" json:"name"`
+	ProtocolType string      `yaml:"protocol_type" json:"protocol_type"`
+	Address      Address     `yaml:"address" json:"address"`
+	FilterChain  FilterChain `yaml:"filter_chains" json:"filter_chains"`
+	Config       interface{} `yaml:"config,omitempty" json:"config,omitempty"`
+}
+
+type Address struct {
+	SocketAddress SocketAddress `yaml:"socket_address" json:"socket_address"`
+}
+
+type SocketAddress struct {
+	Address string `yaml:"address" json:"address"`
+	Port    int    `yaml:"port" json:"port"`
+}
+
+type FilterChain struct {
+	Filters []NetworkFilter `yaml:"filters" json:"filters"`
+}
+
+type NetworkFilter struct {
+	Name   string      `yaml:"name" json:"name"`
+	Config interface{} `yaml:"config" json:"config"`
+}
+
+type HTTPConnectionManagerConfig struct {
+	RouteConfig RouteConfiguration `yaml:"route_config" json:"route_config"`
+	HTTPFilters []HTTPFilter       `yaml:"http_filters" json:"http_filters"`
+}
+
+type RouteConfiguration struct {
+	Routes []*Route `yaml:"routes" json:"routes"`
+}
+
+type Route struct {
+	Match RouteMatch  `yaml:"match" json:"match"`
+	Route RouteAction `yaml:"route" json:"route"`
+}
+
+type RouteMatch struct {
+	Prefix  string          `yaml:"prefix,omitempty" json:"prefix,omitempty"`
+	Path    string          `yaml:"path,omitempty" json:"path,omitempty"`
+	Methods []string        `yaml:"methods,omitempty" json:"methods,omitempty"`
+	Headers []HeaderMatcher `yaml:"headers,omitempty" json:"headers,omitempty"`
+}
+
+type HeaderMatcher struct {
+	Name   string   `yaml:"name" json:"name"`
+	Values []string `yaml:"values,omitempty" json:"values,omitempty"`
+	Regex  bool     `yaml:"regex,omitempty" json:"regex,omitempty"`
+}
+
+type RouteAction struct {
+	Cluster                     string `yaml:"cluster" json:"cluster"`
+	ClusterNotFoundResponseCode int    `yaml:"cluster_not_found_response_code,omitempty" json:"cluster_not_found_response_code,omitempty"`
+}
+
+type HTTPFilter struct {
+	Name   string                 `yaml:"name" json:"name"`
+	Config map[string]interface{} `yaml:"config,omitempty" json:"config,omitempty"`
+}
+
+type Cluster struct {
+	Name      string      `yaml:"name" json:"name"`
+	Type      string      `yaml:"type" json:"type"`
+	LbPolicy  string      `yaml:"lb_policy" json:"lb_policy"`
+	Endpoints []*Endpoint `yaml:"endpoints" json:"endpoints"`
+}
+
+type Endpoint struct {
+	ID            int           `yaml:"id" json:"id"`
+	SocketAddress SocketAddress `yaml:"socket_address" json:"socket_address"`
+}
+
+type ShutdownConfig struct {
+	Timeout      string `yaml:"timeout" json:"timeout"`
+	StepTimeout  string `yaml:"step_timeout" json:"step_timeout"`
+	RejectPolicy string `yaml:"reject_policy" json:"reject_policy"`
+}
+
+type LogConfig struct {
+	Level string `yaml:"level" json:"level"`
+}
+
+type DynamicResources struct {
+	LdsConfig *ApiConfigSource `yaml:"lds_config,omitempty" json:"lds_config,omitempty"`
+	CdsConfig *ApiConfigSource `yaml:"cds_config,omitempty" json:"cds_config,omitempty"`
+}
+
+type ApiConfigSource struct {
+	ClusterName    []string `yaml:"cluster_name" json:"cluster_name"`
+	APIType        string   `yaml:"api_type" json:"api_type"`
+	RefreshDelay   string   `yaml:"refresh_delay,omitempty" json:"refresh_delay,omitempty"`
+	RequestTimeout string   `yaml:"request_timeout,omitempty" json:"request_timeout,omitempty"`
+	GrpcServices   []struct {
+		Timeout string `yaml:"timeout,omitempty" json:"timeout,omitempty"`
+	} `yaml:"grpc_services,omitempty" json:"grpc_services,omitempty"`
+}
diff --git a/controllers/internal/ir/xds.go b/controllers/internal/ir/xds.go
new file mode 100644
index 00000000..7b7f394a
--- /dev/null
+++ b/controllers/internal/ir/xds.go
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ir
+
+// Xds holds the intermediate representation of a Gateway and is
+// used by the Converter to convert it into Pixiu conf.yaml resources.
+// This is similar to Envoy Gateway's IR.Xds but adapted for Pixiu.
+type Xds struct {
+	HTTP []*HTTPListener `json:"http,omitempty" yaml:"http,omitempty"`
+	TCP  []*TCPListener  `json:"tcp,omitempty" yaml:"tcp,omitempty"`
+	UDP  []*UDPListener  `json:"udp,omitempty" yaml:"udp,omitempty"`
+}
+
+type HTTPListener struct {
+	Name      string       `json:"name" yaml:"name"`
+	Address   string       `json:"address" yaml:"address"`
+	Port      int32        `json:"port" yaml:"port"`
+	Hostnames []string     `json:"hostnames,omitempty" yaml:"hostnames,omitempty"`
+	Routes    []*HTTPRoute `json:"routes,omitempty" yaml:"routes,omitempty"`
+}
+
+type TCPListener struct {
+	Name    string      `json:"name" yaml:"name"`
+	Address string      `json:"address" yaml:"address"`
+	Port    int32       `json:"port" yaml:"port"`
+	Routes  []*TCPRoute `json:"routes,omitempty" yaml:"routes,omitempty"`
+}
+
+type UDPListener struct {
+	Name    string `json:"name" yaml:"name"`
+	Address string `json:"address" yaml:"address"`
+	Port    int32  `json:"port" yaml:"port"`
+}
+
+type HTTPRoute struct {
+	Name          string              `json:"name" yaml:"name"`
+	PathMatch     *StringMatch        `json:"pathMatch,omitempty" yaml:"pathMatch,omitempty"`
+	HeaderMatches []*StringMatch      `json:"headerMatches,omitempty" yaml:"headerMatches,omitempty"`
+	Method        *string             `json:"method,omitempty" yaml:"method,omitempty"`
+	Destinations  []*RouteDestination `json:"destinations,omitempty" yaml:"destinations,omitempty"`
+	Filters       []*HTTPFilter       `json:"filters,omitempty" yaml:"filters,omitempty"`
+}
+
+type TCPRoute struct {
+	Name         string              `json:"name" yaml:"name"`
+	Destinations []*RouteDestination `json:"destinations,omitempty" yaml:"destinations,omitempty"`
+}
+
+type StringMatch struct {
+	Exact     *string `json:"exact,omitempty" yaml:"exact,omitempty"`
+	Prefix    *string `json:"prefix,omitempty" yaml:"prefix,omitempty"`
+	SafeRegex *string `json:"safeRegex,omitempty" yaml:"safeRegex,omitempty"`
+	Name      *string `json:"name,omitempty" yaml:"name,omitempty"`
+}
+
+type RouteDestination struct {
+	Name   string  `json:"name" yaml:"name"`
+	Weight *uint32 `json:"weight,omitempty" yaml:"weight,omitempty"`
+}
+
+type HTTPFilter struct {
+	Name   string                 `json:"name" yaml:"name"`
+	Config map[string]interface{} `json:"config,omitempty" yaml:"config,omitempty"`
+}
+
+type Cluster struct {
+	Name               string              `json:"name" yaml:"name"`
+	Endpoints          []*Endpoint         `json:"endpoints,omitempty" yaml:"endpoints,omitempty"`
+	LoadBalancerPolicy *LoadBalancerPolicy `json:"loadBalancerPolicy,omitempty" yaml:"loadBalancerPolicy,omitempty"`
+}
+
+type Endpoint struct {
+	ID      int    `json:"id" yaml:"id"`
+	Address string `json:"address" yaml:"address"`
+	Port    int32  `json:"port" yaml:"port"`
+}
+
+type LoadBalancerPolicy struct {
+	Type string `json:"type" yaml:"type"`
+}
diff --git a/controllers/internal/manager/controllers.go b/controllers/internal/manager/controllers.go
index fee87445..eaa7b8b9 100644
--- a/controllers/internal/manager/controllers.go
+++ b/controllers/internal/manager/controllers.go
@@ -46,10 +46,6 @@ import (
 // +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=httproutes/status,verbs=get;update
 // +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=referencegrants,verbs=list;watch;update
 // +kubebuilder:rbac:groups=gateway.networking.k8s.io,resources=referencegrants/status,verbs=get;update
-// Networking
-// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses,verbs=get;list;watch;update
-// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingresses/status,verbs=get;update
-// +kubebuilder:rbac:groups=networking.k8s.io,resources=ingressclasses,verbs=get;list;watch
 type Controller interface {
 	SetupWithManager(mgr manager.Manager) error
 }
@@ -71,21 +67,5 @@ func setupControllers(ctx context.Context, mgr manager.Manager, updater status.U
 			Log:     ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Gateway"),
 			Updater: updater,
 		},
-		&controller.IngressReconciler{
-			Client:  mgr.GetClient(),
-			Scheme:  mgr.GetScheme(),
-			Log:     ctrl.LoggerFrom(ctx).WithName("controllers").WithName("Ingress"),
-			Updater: updater,
-		},
-		&controller.IngressClassReconciler{
-			Client: mgr.GetClient(),
-			Scheme: mgr.GetScheme(),
-			Log:    ctrl.LoggerFrom(ctx).WithName("controllers").WithName("IngressClass"),
-		},
-		&controller.GatewayProxyController{
-			Client: mgr.GetClient(),
-			Scheme: mgr.GetScheme(),
-			Log:    ctrl.LoggerFrom(ctx).WithName("controllers").WithName("GatewayProxy"),
-		},
 	}, nil
 }
diff --git a/controllers/internal/translator/translator.go b/controllers/internal/translator/translator.go
new file mode 100644
index 00000000..5b7832b8
--- /dev/null
+++ b/controllers/internal/translator/translator.go
@@ -0,0 +1,350 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package translator
+
+import (
+	"context"
+	"fmt"
+)
+
+import (
+	"controllers/internal/ir"
+
+	"github.com/go-logr/logr"
+
+	corev1 "k8s.io/api/core/v1"
+
+	discoveryv1 "k8s.io/api/discovery/v1"
+
+	"sigs.k8s.io/controller-runtime/pkg/client"
+
+	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
+)
+
+type Translator struct {
+	client client.Client
+	logger logr.Logger
+}
+
+func NewTranslator(client client.Client, logger logr.Logger) *Translator {
+	return &Translator{
+		client: client,
+		logger: logger,
+	}
+}
+
+func (t *Translator) TranslateGateway(ctx context.Context, gateway *gatewayv1.Gateway) (*ir.Xds, error) {
+	xds := &ir.Xds{
+		HTTP: []*ir.HTTPListener{},
+		TCP:  []*ir.TCPListener{},
+		UDP:  []*ir.UDPListener{},
+	}
+
+	var httpRouteList gatewayv1.HTTPRouteList
+	if err := t.client.List(ctx, &httpRouteList); err != nil {
+		return nil, fmt.Errorf("failed to list HTTPRoutes: %w", err)
+	}
+
+	for _, listener := range gateway.Spec.Listeners {
+		if listener.Protocol == gatewayv1.HTTPProtocolType || listener.Protocol == gatewayv1.HTTPSProtocolType {
+			httpListener := t.translateHTTPListener(&listener, gateway, &httpRouteList)
+			if httpListener != nil {
+				xds.HTTP = append(xds.HTTP, httpListener)
+			}
+		} else if listener.Protocol == gatewayv1.TCPProtocolType {
+			tcpListener := t.translateTCPListener(&listener, gateway)
+			if tcpListener != nil {
+				xds.TCP = append(xds.TCP, tcpListener)
+			}
+		} else if listener.Protocol == gatewayv1.UDPProtocolType {
+			udpListener := t.translateUDPListener(&listener, gateway)
+			if udpListener != nil {
+				xds.UDP = append(xds.UDP, udpListener)
+			}
+		}
+	}
+
+	return xds, nil
+}
+
+func (t *Translator) translateHTTPListener(listener *gatewayv1.Listener, gateway *gatewayv1.Gateway, httpRouteList *gatewayv1.HTTPRouteList) *ir.HTTPListener {
+	address := "0.0.0.0"
+	if len(gateway.Spec.Addresses) > 0 {
+		address = gateway.Spec.Addresses[0].Value
+	}
+
+	// Pixiu gateway listens on port 8888 internally
+	// Gateway listener port is mapped via Service
+	httpListener := &ir.HTTPListener{
+		Name:    string(listener.Name),
+		Address: address,
+		Port:    8888, // Pixiu gateway default port
+		Routes:  []*ir.HTTPRoute{},
+	}
+
+	matchedRoutes := 0
+	for _, httpRoute := range httpRouteList.Items {
+		attached := false
+		for _, parentRef := range httpRoute.Spec.ParentRefs {
+			if parentRef.Group != nil && *parentRef.Group != gatewayv1.GroupName {
+				continue
+			}
+			if parentRef.Kind != nil && *parentRef.Kind != "Gateway" {
+				continue
+			}
+			if string(parentRef.Name) != gateway.Name {
+				continue
+			}
+
+			namespaceMatch := false
+			if parentRef.Namespace != nil {
+				namespaceMatch = string(*parentRef.Namespace) == gateway.Namespace
+			} else {
+				namespaceMatch = gateway.Namespace == httpRoute.Namespace
+			}
+			if !namespaceMatch {
+				continue
+			}
+
+			if parentRef.SectionName != nil && *parentRef.SectionName != listener.Name {
+				continue
+			}
+
+			attached = true
+			break
+		}
+		if !attached {
+			continue
+		}
+
+		matchedRoutes++
+		for _, rule := range httpRoute.Spec.Rules {
+			for _, match := range rule.Matches {
+				irRoute := t.translateHTTPRouteMatch(&match, &httpRoute, rule.BackendRefs)
+				if irRoute != nil {
+					httpListener.Routes = append(httpListener.Routes, irRoute)
+				}
+			}
+		}
+	}
+
+	return httpListener
+}
+
+func (t *Translator) translateHTTPRouteMatch(match *gatewayv1.HTTPRouteMatch, httpRoute *gatewayv1.HTTPRoute, backendRefs []gatewayv1.HTTPBackendRef) *ir.HTTPRoute {
+	pathTypeStr := "PathPrefix"
+	if match.Path != nil && match.Path.Type != nil {
+		pathTypeStr = string(*match.Path.Type)
+	}
+	irRoute := &ir.HTTPRoute{
+		Name: fmt.Sprintf("%s-%s", httpRoute.Name, pathTypeStr),
+	}
+
+	if match.Path != nil {
+		if match.Path.Type != nil && match.Path.Value != nil {
+			pathType := string(*match.Path.Type)
+			pathValue := *match.Path.Value
+			switch pathType {
+			case string(gatewayv1.PathMatchExact):
+				irRoute.PathMatch = &ir.StringMatch{
+					Exact: &pathValue,
+				}
+			case string(gatewayv1.PathMatchPathPrefix):
+				irRoute.PathMatch = &ir.StringMatch{
+					Prefix: &pathValue,
+				}
+			case string(gatewayv1.PathMatchRegularExpression):
+				irRoute.PathMatch = &ir.StringMatch{
+					SafeRegex: &pathValue,
+				}
+			}
+		}
+	} else {
+		// Default to prefix "/"
+		prefix := "/"
+		irRoute.PathMatch = &ir.StringMatch{
+			Prefix: &prefix,
+		}
+	}
+
+	if match.Method != nil {
+		method := string(*match.Method)
+		irRoute.Method = &method
+	}
+
+	if len(match.Headers) > 0 {
+		irRoute.HeaderMatches = []*ir.StringMatch{}
+		for _, headerMatch := range match.Headers {
+			headerType := string(*headerMatch.Type)
+			headerName := string(headerMatch.Name)
+			headerValue := headerMatch.Value
+			var stringMatch *ir.StringMatch
+			switch headerType {
+			case string(gatewayv1.HeaderMatchExact):
+				stringMatch = &ir.StringMatch{
+					Name:  &headerName,
+					Exact: &headerValue,
+				}
+			case string(gatewayv1.HeaderMatchRegularExpression):
+				stringMatch = &ir.StringMatch{
+					Name:      &headerName,
+					SafeRegex: &headerValue,
+				}
+			}
+			if stringMatch != nil {
+				irRoute.HeaderMatches = append(irRoute.HeaderMatches, stringMatch)
+			}
+		}
+	}
+
+	irRoute.Destinations = []*ir.RouteDestination{}
+	for _, backendRef := range backendRefs {
+		if backendRef.Kind != nil && *backendRef.Kind != "Service" {
+			continue
+		}
+		if backendRef.Group != nil && *backendRef.Group != "" {
+			continue
+		}
+		namespace := httpRoute.Namespace
+		if backendRef.Namespace != nil {
+			namespace = string(*backendRef.Namespace)
+		}
+		clusterName := fmt.Sprintf("%s-%s", namespace, string(backendRef.Name))
+		irRoute.Destinations = append(irRoute.Destinations, &ir.RouteDestination{
+			Name: clusterName,
+		})
+	}
+
+	return irRoute
+}
+
+func (t *Translator) translateTCPListener(listener *gatewayv1.Listener, gateway *gatewayv1.Gateway) *ir.TCPListener {
+	address := "0.0.0.0"
+	if len(gateway.Spec.Addresses) > 0 {
+		address = gateway.Spec.Addresses[0].Value
+	}
+
+	return &ir.TCPListener{
+		Name:    string(listener.Name),
+		Address: address,
+		Port:    int32(listener.Port),
+	}
+}
+
+func (t *Translator) translateUDPListener(listener *gatewayv1.Listener, gateway *gatewayv1.Gateway) *ir.UDPListener {
+	address := "0.0.0.0"
+	if len(gateway.Spec.Addresses) > 0 {
+		address = gateway.Spec.Addresses[0].Value
+	}
+
+	return &ir.UDPListener{
+		Name:    string(listener.Name),
+		Address: address,
+		Port:    int32(listener.Port),
+	}
+}
+
+func (t *Translator) TranslateBackendToCluster(ctx context.Context, httpRoute *gatewayv1.HTTPRoute) ([]*ir.Cluster, error) {
+	clusters := []*ir.Cluster{}
+	clusterMap := make(map[string]*ir.Cluster)
+
+	for _, rule := range httpRoute.Spec.Rules {
+		for _, backendRef := range rule.BackendRefs {
+			namespace := httpRoute.Namespace
+			if backendRef.Namespace != nil {
+				namespace = string(*backendRef.Namespace)
+			}
+
+			clusterName := fmt.Sprintf("%s-%s", namespace, string(backendRef.Name))
+			port := int32(80)
+			if backendRef.Port != nil {
+				port = int32(*backendRef.Port)
+			}
+
+			endpoints, err := t.resolveEndpoints(ctx, namespace, string(backendRef.Name), port)
+			if err != nil {
+				t.logger.Error(err, "failed to resolve endpoints", "namespace", namespace, "service", backendRef.Name)
+				continue
+			}
+
+			if existing, exists := clusterMap[clusterName]; exists {
+				// Merge endpoints
+				existing.Endpoints = append(existing.Endpoints, endpoints...)
+			} else {
+				cluster := &ir.Cluster{
+					Name:      clusterName,
+					Endpoints: endpoints,
+				}
+				clusterMap[clusterName] = cluster
+				clusters = append(clusters, cluster)
+			}
+		}
+	}
+
+	return clusters, nil
+}
+
+func (t *Translator) resolveEndpoints(ctx context.Context, namespace, serviceName string, port int32) ([]*ir.Endpoint, error) {
+	var service corev1.Service
+	if err := t.client.Get(ctx, client.ObjectKey{
+		Namespace: namespace,
+		Name:      serviceName,
+	}, &service); err != nil {
+		return nil, fmt.Errorf("failed to get service: %w", err)
+	}
+
+	var endpointSliceList discoveryv1.EndpointSliceList
+	if err := t.client.List(ctx, &endpointSliceList, client.MatchingLabels{
+		discoveryv1.LabelServiceName: serviceName,
+	}, client.InNamespace(namespace)); err != nil {
+		return nil, fmt.Errorf("failed to list endpoint slices: %w", err)
+	}
+
+	endpoints := []*ir.Endpoint{}
+	for _, endpointSlice := range endpointSliceList.Items {
+		for _, endpoint := range endpointSlice.Endpoints {
+			if endpoint.Conditions.Ready != nil && !*endpoint.Conditions.Ready {
+				continue
+			}
+			for _, address := range endpoint.Addresses {
+				targetPort := port
+				if len(endpointSlice.Ports) > 0 {
+					for _, endpointPort := range endpointSlice.Ports {
+						if endpointPort.Port != nil {
+							targetPort = int32(*endpointPort.Port)
+							break
+						}
+					}
+				}
+				endpoints = append(endpoints, &ir.Endpoint{
+					Address: address,
+					Port:    targetPort,
+				})
+			}
+		}
+	}
+
+	if len(endpoints) == 0 && service.Spec.ClusterIP != "" && service.Spec.ClusterIP != "None" {
+		endpoints = append(endpoints, &ir.Endpoint{
+			Address: service.Spec.ClusterIP,
+			Port:    port,
+		})
+	}
+
+	return endpoints, nil
+}
diff --git a/controllers/internal/types/k8s.go b/controllers/internal/types/k8s.go
index 2ec9c608..8231fa5e 100644
--- a/controllers/internal/types/k8s.go
+++ b/controllers/internal/types/k8s.go
@@ -18,22 +18,15 @@
 package types
 
 import (
-	"controllers/api/v1alpha1"
-
 	corev1 "k8s.io/api/core/v1"
 
-	netv1 "k8s.io/api/networking/v1"
-
 	gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
 )
 
 const (
 	KindGateway      = "Gateway"
 	KindGatewayClass = "GatewayClass"
-	KindIngress      = "Ingress"
 	KindSecret       = "Secret"
-	KindIngressClass = "IngressClass"
-	KindGatewayProxy = "GatewayProxy"
 )
 
 func KindOf(obj any) string {
@@ -42,14 +35,8 @@ func KindOf(obj any) string {
 		return KindGateway
 	case *gatewayv1.GatewayClass:
 		return KindGatewayClass
-	case *netv1.Ingress:
-		return KindIngress
-	case *netv1.IngressClass:
-		return KindIngressClass
 	case *corev1.Secret:
 		return KindSecret
-	case *v1alpha1.GatewayProxy:
-		return KindGatewayProxy
 	default:
 		return "Unknown"
 	}
diff --git a/controllers/samples/backend.yaml b/controllers/samples/backend.yaml
new file mode 100644
index 00000000..1b23e506
--- /dev/null
+++ b/controllers/samples/backend.yaml
@@ -0,0 +1,80 @@
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: backend
+  namespace: default
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: backend
+  namespace: default
+  labels:
+    app: backend
+    service: backend
+spec:
+  ports:
+    - name: http
+      port: 3000
+      targetPort: 3000
+  selector:
+    app: backend
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: backend
+  namespace: default
+spec:
+  replicas: 1
+  selector:
+    matchLabels:
+      app: backend
+      version: v1
+  template:
+    metadata:
+      labels:
+        app: backend
+        version: v1
+    spec:
+      serviceAccountName: backend
+      containers:
+        - image: gcr.io/k8s-staging-gateway-api/echo-basic:v20231214-v1.0.0-140-gf544a46e
+          imagePullPolicy: IfNotPresent
+          name: backend
+          ports:
+            - containerPort: 3000
+          env:
+            - name: POD_NAME
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.name
+            - name: NAMESPACE
+              valueFrom:
+                fieldRef:
+                  fieldPath: metadata.namespace
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: HTTPRoute
+metadata:
+  name: backend
+  namespace: default
+spec:
+  parentRefs:
+    - name: pg
+      namespace: pixiu-gateway-system
+  hostnames:
+    - "www.example.com"
+  rules:
+    - backendRefs:
+        - group: ""
+          kind: Service
+          name: backend
+          port: 3000
+          weight: 1
+      matches:
+        - path:
+            type: PathPrefix
+            value: /
+
diff --git a/controllers/samples/controller.yaml b/controllers/samples/controller.yaml
new file mode 100644
index 00000000..1a15874b
--- /dev/null
+++ b/controllers/samples/controller.yaml
@@ -0,0 +1,245 @@
+---
+apiVersion: v1
+kind: Namespace
+metadata:
+  name: pixiu-gateway-system
+  labels:
+    control-plane: controller-manager
+    app.kubernetes.io/name: pg-controller
+---
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: pg-controller
+  namespace: pixiu-gateway-system
+  labels:
+    app.kubernetes.io/name: pg-controller
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRole
+metadata:
+  name: pg-controller-role
+  labels:
+    app.kubernetes.io/name: pg-controller
+rules:
+- apiGroups:
+  - ""
+  resources:
+  - events
+  verbs:
+  - create
+  - patch
+- apiGroups:
+  - ""
+  resources:
+  - namespaces
+  - pods
+  - services
+  - configmaps
+  verbs:
+  - get
+  - list
+  - watch
+  - create
+  - update
+  - patch
+  - delete
+- apiGroups:
+  - apps
+  resources:
+  - deployments
+  verbs:
+  - get
+  - list
+  - watch
+  - create
+  - update
+  - patch
+  - delete
+- apiGroups:
+  - coordination.k8s.io
+  resources:
+  - leases
+  verbs:
+  - create
+  - delete
+  - get
+  - list
+  - patch
+  - update
+  - watch
+- apiGroups:
+  - discovery.k8s.io
+  resources:
+  - endpointslices
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - gateway.networking.k8s.io
+  resources:
+  - gatewayclasses
+  - gateways
+  verbs:
+  - get
+  - list
+  - watch
+  - update
+  - patch
+- apiGroups:
+  - gateway.networking.k8s.io
+  resources:
+  - gatewayclasses/status
+  - gateways/status
+  - httproutes/status
+  - referencegrants/status
+  verbs:
+  - get
+  - update
+  - patch
+- apiGroups:
+  - gateway.networking.k8s.io
+  resources:
+  - httproutes
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+  - gateway.networking.k8s.io
+  resources:
+  - referencegrants
+  verbs:
+  - get
+  - list
+  - watch
+- apiGroups:
+    - ""
+  resources:
+    - secrets
+  verbs:
+    - get
+    - list
+    - watch
+---
+apiVersion: rbac.authorization.k8s.io/v1
+kind: ClusterRoleBinding
+metadata:
+  name: pg-controller-rolebinding
+  labels:
+    app.kubernetes.io/name: pg-controller
+roleRef:
+  apiGroup: rbac.authorization.k8s.io
+  kind: ClusterRole
+  name: pg-controller-role
+subjects:
+- kind: ServiceAccount
+  name: pg-controller
+  namespace: pixiu-gateway-system
+---
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: pg-controller
+  namespace: pixiu-gateway-system
+  labels:
+    control-plane: controller-manager
+    app.kubernetes.io/name: pg-controller
+spec:
+  selector:
+    matchLabels:
+      control-plane: controller-manager
+      app.kubernetes.io/name: pg-controller
+  replicas: 1
+  template:
+    metadata:
+      annotations:
+        kubectl.kubernetes.io/default-container: manager
+      labels:
+        control-plane: controller-manager
+        app.kubernetes.io/name: pg-controller
+    spec:
+      securityContext:
+        runAsNonRoot: true
+        seccompProfile:
+          type: RuntimeDefault
+      containers:
+      - command:
+            - /usr/local/bin/pg-controller
+        args:
+        - --config-path=/config/pg.yaml
+        - --log-level=info
+        image: mfordjody/pixiugateway-controller:debug
+        imagePullPolicy: Always
+        name: manager
+        ports:
+        - containerPort: 8081
+          name: health
+          protocol: TCP
+        - containerPort: 8080
+          name: metrics
+          protocol: TCP
+        volumeMounts:
+        - name: config
+          mountPath: /config
+          readOnly: true
+        securityContext:
+          readOnlyRootFilesystem: true
+          allowPrivilegeEscalation: false
+          capabilities:
+            drop:
+            - "ALL"
+        livenessProbe:
+          httpGet:
+            path: /healthz
+            port: 8081
+          initialDelaySeconds: 15
+          periodSeconds: 20
+        readinessProbe:
+          httpGet:
+            path: /readyz
+            port: 8081
+          initialDelaySeconds: 5
+          periodSeconds: 10
+        resources:
+          limits:
+            cpu: 500m
+            memory: 512Mi
+          requests:
+            cpu: 10m
+            memory: 64Mi
+      volumes:
+      - name: config
+        configMap:
+          name: pg
+      serviceAccountName: pg-controller
+      terminationGracePeriodSeconds: 10
+---
+apiVersion: v1
+kind: Service
+metadata:
+  name: pg-controller
+  namespace: pixiu-gateway-system
+spec:
+  selector:
+    app.kubernetes.io/name: pg-controller
+  ports:
+    - name: metrics
+      protocol: TCP
+      port: 8080
+      targetPort: 8080
+    - name: health
+      protocol: TCP
+      port: 8081
+      targetPort: 8081
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+  name: pg
+  namespace: pixiu-gateway-system
+data:
+  pg.yaml: |
+    log_level: info
+    controller_name: pixiu.apache.org/pixiu-gateway-controller
diff --git a/controllers/samples/pixiugateway.yaml b/controllers/samples/pixiugateway.yaml
new file mode 100644
index 00000000..bcfa04c9
--- /dev/null
+++ b/controllers/samples/pixiugateway.yaml
@@ -0,0 +1,18 @@
+apiVersion: gateway.networking.k8s.io/v1
+kind: GatewayClass
+metadata:
+  name: pg
+spec:
+  controllerName: pixiu.apache.org/pixiu-gateway-controller
+---
+apiVersion: gateway.networking.k8s.io/v1
+kind: Gateway
+metadata:
+  name: pg
+  namespace: pixiu-gateway-system
+spec:
+  gatewayClassName: pg
+  listeners:
+    - name: http
+      protocol: HTTP
+      port: 80
diff --git a/pkg/common/constant/http.go b/pkg/common/constant/http.go
index 2156ab90..1ec456b8 100644
--- a/pkg/common/constant/http.go
+++ b/pkg/common/constant/http.go
@@ -57,7 +57,8 @@ const (
 
 	HeaderValueAuthorization = "Authorization"
 
-	HeaderValueAll = "*"
+	HeaderValueAll       = "*"
+	HeaderValueAllLevels = "**"
 
 	PathSlash           = "/"
 	ProtocolSlash       = "://"
diff --git a/pkg/common/router/mock/router.go b/pkg/common/router/mock/router.go
new file mode 100644
index 00000000..317f37e4
--- /dev/null
+++ b/pkg/common/router/mock/router.go
@@ -0,0 +1,177 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package mock
+
+import (
+	stdHttp "net/http"
+	"strings"
+	"sync"
+)
+
+import (
+	"github.com/pkg/errors"
+)
+
+import (
+	"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
+	"github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
+	"github.com/apache/dubbo-go-pixiu/pkg/common/util/stringutil"
+	"github.com/apache/dubbo-go-pixiu/pkg/context/http"
+	"github.com/apache/dubbo-go-pixiu/pkg/logger"
+	"github.com/apache/dubbo-go-pixiu/pkg/model"
+	"github.com/apache/dubbo-go-pixiu/pkg/server"
+)
+
+// * =================================
+// This is a old implementation before updating to the new router framework (#777)
+// It is a reserved for function verification only
+// DO NOT USE IT IN ANYWHERE BUT TEST IN pkg/common/router/router_test.go
+// * =================================
+type (
+	// RouterCoordinator the router coordinator for http connection manager
+	RouterCoordinator struct {
+		activeConfig *model.RouteConfiguration
+		rw           sync.RWMutex
+	}
+)
+
+// CreateRouterCoordinator create coordinator for http connection manager
+func CreateRouterCoordinator(routeConfig *model.RouteConfiguration) *RouterCoordinator {
+	rc := &RouterCoordinator{activeConfig: routeConfig}
+	if routeConfig.Dynamic {
+		server.GetRouterManager().AddRouterListener(rc)
+	}
+	rc.initTrie()
+	rc.initRegex()
+	return rc
+}
+
+// Route find routeAction for request
+func (rm *RouterCoordinator) Route(hc *http.HttpContext) (*model.RouteAction, error) {
+	rm.rw.RLock()
+	defer rm.rw.RUnlock()
+
+	return rm.route(hc.Request)
+}
+
+func (rm *RouterCoordinator) RouteByPathAndName(path, method string) (*model.RouteAction, error) {
+	rm.rw.RLock()
+	defer rm.rw.RUnlock()
+
+	return rm.activeConfig.RouteByPathAndMethod(path, method)
+}
+
+func (rm *RouterCoordinator) route(req *stdHttp.Request) (*model.RouteAction, error) {
+	// match those route that only contains headers first
+	var matched []*model.Router
+	for _, route := range rm.activeConfig.Routes {
+		if len(route.Match.Prefix) > 0 {
+			continue
+		}
+		if route.Match.MatchHeader(req) {
+			matched = append(matched, route)
+		}
+	}
+
+	// always return the first match of header if got any
+	if len(matched) > 0 {
+		if len(matched[0].Route.Cluster) == 0 {
+			return nil, errors.New("action is nil. please check your configuration.")
+		}
+		return &matched[0].Route, nil
+	}
+
+	// match those route that only contains prefix
+	// TODO: may consider implementing both prefix and header in the future
+	return rm.activeConfig.Route(req)
+}
+
+func getTrieKey(method string, path string, isPrefix bool) string {
+	if isPrefix {
+		if !strings.HasSuffix(path, constant.PathSlash) {
+			path = path + constant.PathSlash
+		}
+		path = path + "**"
+	}
+	return stringutil.GetTrieKey(method, path)
+}
+
+func (rm *RouterCoordinator) initTrie() {
+	if rm.activeConfig.RouteTrie.IsEmpty() {
+		rm.activeConfig.RouteTrie = trie.NewTrie()
+	}
+	for _, router := range rm.activeConfig.Routes {
+		rm.OnAddRouter(router)
+	}
+}
+
+func (rm *RouterCoordinator) initRegex() {
+	for _, router := range rm.activeConfig.Routes {
+		headers := router.Match.Headers
+		for i := range headers {
+			if headers[i].Regex && len(headers[i].Values) > 0 {
+				// regexp always use first value of header
+				err := headers[i].SetValueRegex(headers[i].Values[0])
+				if err != nil {
+					logger.Errorf("invalid regexp in headers[%d]: %v", i, err)
+					panic(err)
+				}
+			}
+		}
+	}
+}
+
+// OnAddRouter add router
+func (rm *RouterCoordinator) OnAddRouter(r *model.Router) {
+	//TODO: lock move to trie node
+	rm.rw.Lock()
+	defer rm.rw.Unlock()
+	if r.Match.Methods == nil {
+		r.Match.Methods = []string{constant.Get, constant.Put, constant.Delete, constant.Post, constant.Options}
+	}
+	isPrefix := r.Match.Prefix != ""
+	for _, method := range r.Match.Methods {
+		var key string
+		if isPrefix {
+			key = getTrieKey(method, r.Match.Prefix, isPrefix)
+		} else {
+			key = getTrieKey(method, r.Match.Path, isPrefix)
+		}
+		_, _ = rm.activeConfig.RouteTrie.Put(key, r.Route)
+	}
+}
+
+// OnDeleteRouter delete router
+func (rm *RouterCoordinator) OnDeleteRouter(r *model.Router) {
+	rm.rw.Lock()
+	defer rm.rw.Unlock()
+
+	if r.Match.Methods == nil {
+		r.Match.Methods = []string{constant.Get, constant.Put, constant.Delete, constant.Post}
+	}
+	isPrefix := r.Match.Prefix != ""
+	for _, method := range r.Match.Methods {
+		var key string
+		if isPrefix {
+			key = getTrieKey(method, r.Match.Prefix, isPrefix)
+		} else {
+			key = getTrieKey(method, r.Match.Path, isPrefix)
+		}
+		_, _ = rm.activeConfig.RouteTrie.Remove(key)
+	}
+}
diff --git a/pkg/common/router/router.go b/pkg/common/router/router.go
index cd175eb2..6a8393c5 100644
--- a/pkg/common/router/router.go
+++ b/pkg/common/router/router.go
@@ -19,8 +19,10 @@ package router
 
 import (
 	stdHttp "net/http"
-	"strings"
+	"slices"
 	"sync"
+	"sync/atomic"
+	"time"
 )
 
 import (
@@ -28,7 +30,6 @@ import (
 )
 
 import (
-	"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
 	"github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
 	"github.com/apache/dubbo-go-pixiu/pkg/common/util/stringutil"
 	"github.com/apache/dubbo-go-pixiu/pkg/context/http"
@@ -37,136 +38,216 @@ import (
 	"github.com/apache/dubbo-go-pixiu/pkg/server"
 )
 
-type (
-	// RouterCoordinator the router coordinator for http connection manager
-	RouterCoordinator struct {
-		activeConfig *model.RouteConfiguration
-		rw           sync.RWMutex
-	}
-)
+// RouterCoordinator the router coordinator for http connection manager
+type RouterCoordinator struct {
+	mainSnapshot atomic.Pointer[model.RouteSnapshot] // atomic snapshot
+	mu           sync.Mutex
+
+	nextSnapshot []*model.Router // temp store for dynamic update, DO NOT read directly
+
+	timer    *time.Timer   // debounce timer
+	debounce time.Duration // merge window, default 50ms
+}
 
 // CreateRouterCoordinator create coordinator for http connection manager
 func CreateRouterCoordinator(routeConfig *model.RouteConfiguration) *RouterCoordinator {
-	rc := &RouterCoordinator{activeConfig: routeConfig}
+	rc := &RouterCoordinator{
+		nextSnapshot: make([]*model.Router, 0, len(routeConfig.Routes)),
+		debounce:     50 * time.Millisecond, // merge window
+	}
 	if routeConfig.Dynamic {
 		server.GetRouterManager().AddRouterListener(rc)
 	}
-	rc.initTrie()
-	rc.initRegex()
+	// build initial config and store snapshot
+	rc.mainSnapshot.Store(model.ToSnapshot(buildRouteConfiguration(routeConfig.Routes).Routes))
+	// copy initial routes to store, keep origin order
+	rc.nextSnapshot = append(rc.nextSnapshot, routeConfig.Routes...)
 	return rc
 }
 
-// Route find routeAction for request
 func (rm *RouterCoordinator) Route(hc *http.HttpContext) (*model.RouteAction, error) {
-	rm.rw.RLock()
-	defer rm.rw.RUnlock()
-
 	return rm.route(hc.Request)
 }
 
 func (rm *RouterCoordinator) RouteByPathAndName(path, method string) (*model.RouteAction, error) {
-	rm.rw.RLock()
-	defer rm.rw.RUnlock()
-
-	return rm.activeConfig.RouteByPathAndMethod(path, method)
+	s := rm.mainSnapshot.Load()
+	if s == nil {
+		return nil, errors.New("router configuration is empty")
+	}
+	t := s.MethodTries[method]
+	if t == nil {
+		return nil, errors.Errorf("route failed for %s, no rules matched", stringutil.GetTrieKey(method, path))
+	}
+	node, _, ok := t.Match(stringutil.GetTrieKey(method, path))
+	if !ok || node == nil || node.GetBizInfo() == nil {
+		return nil, errors.Errorf("route failed for %s, no rules matched", stringutil.GetTrieKey(method, path))
+	}
+	act, ok := node.GetBizInfo().(model.RouteAction)
+	if !ok {
+		return nil, errors.Errorf("route failed for %s, invalid route action type", stringutil.GetTrieKey(method, path))
+	}
+	return &act, nil
 }
 
 func (rm *RouterCoordinator) route(req *stdHttp.Request) (*model.RouteAction, error) {
-	// match those route that only contains headers first
-	var matched []*model.Router
-	for _, route := range rm.activeConfig.Routes {
-		if len(route.Match.Prefix) > 0 {
+	s := rm.mainSnapshot.Load()
+	if s == nil {
+		return nil, errors.New("router configuration is empty")
+	}
+
+	// header-only first
+	for _, hr := range s.HeaderOnly {
+		if !model.MethodAllowed(hr.Methods, req.Method) {
 			continue
 		}
-		if route.Match.MatchHeader(req) {
-			matched = append(matched, route)
+		if matchHeaders(hr.Headers, req) {
+			if len(hr.Action.Cluster) == 0 {
+				return nil, errors.New("action is nil. please check your configuration.")
+			}
+			return &hr.Action, nil
 		}
 	}
+	// Trie
+	t := s.MethodTries[req.Method]
+	if t == nil {
+		return nil, errors.Errorf("route failed for %s, no rules matched", stringutil.GetTrieKey(req.Method, req.URL.Path))
 
-	// always return the first match of header if got any
-	if len(matched) > 0 {
-		if len(matched[0].Route.Cluster) == 0 {
-			return nil, errors.New("action is nil. please check your configuration.")
-		}
-		return &matched[0].Route, nil
 	}
 
-	// match those route that only contains prefix
-	// TODO: may consider implementing both prefix and header in the future
-	return rm.activeConfig.Route(req)
+	node, _, ok := t.Match(stringutil.GetTrieKey(req.Method, req.URL.Path))
+	if !ok || node == nil || node.GetBizInfo() == nil {
+		return nil, errors.Errorf("route failed for %s, no rules matched", stringutil.GetTrieKey(req.Method, req.URL.Path))
+	}
+	act, ok := node.GetBizInfo().(model.RouteAction)
+	if !ok {
+		return nil, errors.Errorf("route failed for %s, invalid route action type", stringutil.GetTrieKey(req.Method, req.URL.Path))
+	}
+	return &act, nil
 }
 
-func getTrieKey(method string, path string, isPrefix bool) string {
-	if isPrefix {
-		if !strings.HasSuffix(path, constant.PathSlash) {
-			path = path + constant.PathSlash
+// reset timer or publish directly
+func (rm *RouterCoordinator) schedulePublishLocked() {
+	if rm.debounce <= 0 {
+		// fallback: immediate
+		rm.publishLocked()
+		return
+	}
+	if rm.timer == nil {
+		rm.timer = time.NewTimer(rm.debounce)
+		go rm.awaitAndPublish()
+		return
+	}
+	// clear timer channel
+	if !rm.timer.Stop() {
+		select {
+		case <-rm.timer.C:
+		default:
 		}
-		path = path + "**"
 	}
-	return stringutil.GetTrieKey(method, path)
+	rm.timer.Reset(rm.debounce)
 }
 
-func (rm *RouterCoordinator) initTrie() {
-	if rm.activeConfig.RouteTrie.IsEmpty() {
-		rm.activeConfig.RouteTrie = trie.NewTrie()
-	}
-	for _, router := range rm.activeConfig.Routes {
-		rm.OnAddRouter(router)
+// wait for timer and publish
+func (rm *RouterCoordinator) awaitAndPublish() {
+	<-rm.timer.C
+	rm.mu.Lock()
+	defer rm.mu.Unlock()
+	rm.publishLocked()
+	rm.timer = nil
+}
+
+// publish: clone from store -> build new config -> atomic switch
+func (rm *RouterCoordinator) publishLocked() {
+	// 1) clone routes
+	next := make([]*model.Router, len(rm.nextSnapshot))
+	copy(next, rm.nextSnapshot)
+	// 2) build new config
+	cfg := buildRouteConfiguration(next)
+	// 3) atomic switch
+	rm.mainSnapshot.Store(model.ToSnapshot(cfg.Routes))
+}
+
+func buildRouteConfiguration(routes []*model.Router) *model.RouteConfiguration {
+	cfg := &model.RouteConfiguration{
+		RouteTrie: trie.NewTrie(),
+		Routes:    make([]*model.Router, 0, len(routes)),
+		Dynamic:   false,
 	}
+	cfg.Routes = append(cfg.Routes, routes...)
+	initRegex(cfg)
+	fillTrieFromRoutes(cfg)
+	return cfg
 }
 
-func (rm *RouterCoordinator) initRegex() {
-	for _, router := range rm.activeConfig.Routes {
+func initRegex(cfg *model.RouteConfiguration) {
+	for _, router := range cfg.Routes {
 		headers := router.Match.Headers
 		for i := range headers {
 			if headers[i].Regex && len(headers[i].Values) > 0 {
-				// regexp always use first value of header
-				err := headers[i].SetValueRegex(headers[i].Values[0])
-				if err != nil {
-					logger.Errorf("invalid regexp in headers[%d]: %v", i, err)
-					panic(err)
+				if err := headers[i].SetValueRegex(headers[i].Values[0]); err != nil {
+					logger.Warnf("invalid regexp in headers[%d]: %v", i, err)
 				}
 			}
 		}
 	}
 }
 
-// OnAddRouter add router
+// OnAddRouter add router, every call of OnAdd will ADD a new rule, instead of OVERWRITE the same rule
 func (rm *RouterCoordinator) OnAddRouter(r *model.Router) {
-	//TODO: lock move to trie node
-	rm.rw.Lock()
-	defer rm.rw.Unlock()
-	if r.Match.Methods == nil {
-		r.Match.Methods = []string{constant.Get, constant.Put, constant.Delete, constant.Post, constant.Options}
-	}
-	isPrefix := r.Match.Prefix != ""
-	for _, method := range r.Match.Methods {
-		var key string
-		if isPrefix {
-			key = getTrieKey(method, r.Match.Prefix, isPrefix)
-		} else {
-			key = getTrieKey(method, r.Match.Path, isPrefix)
+	rm.mu.Lock()
+	defer rm.mu.Unlock()
+	rm.nextSnapshot = append(rm.nextSnapshot, r)
+	rm.schedulePublishLocked()
+}
+
+func fillTrieFromRoutes(cfg *model.RouteConfiguration) {
+	for _, r := range cfg.Routes {
+		methods := r.Match.Methods
+		if len(methods) == 0 {
+			methods = []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"}
+		}
+		for _, m := range methods {
+			key := stringutil.GetTrieKeyWithPrefix(m, r.Match.Path, r.Match.Prefix, r.Match.Prefix != "")
+			_, _ = cfg.RouteTrie.Put(key, r.Route)
 		}
-		_, _ = rm.activeConfig.RouteTrie.Put(key, r.Route)
 	}
 }
 
 // OnDeleteRouter delete router
 func (rm *RouterCoordinator) OnDeleteRouter(r *model.Router) {
-	rm.rw.Lock()
-	defer rm.rw.Unlock()
+	rm.mu.Lock()
+	defer rm.mu.Unlock()
 
-	if r.Match.Methods == nil {
-		r.Match.Methods = []string{constant.Get, constant.Put, constant.Delete, constant.Post}
+	if len(rm.nextSnapshot) == 0 {
+		return
 	}
-	isPrefix := r.Match.Prefix != ""
-	for _, method := range r.Match.Methods {
-		var key string
-		if isPrefix {
-			key = getTrieKey(method, r.Match.Prefix, isPrefix)
+	out := rm.nextSnapshot[:0]
+	for _, rr := range rm.nextSnapshot {
+		if rr.ID == r.ID {
+			continue
+		}
+		out = append(out, rr)
+	}
+	rm.nextSnapshot = out
+	rm.schedulePublishLocked()
+}
+
+func matchHeaders(chs []model.CompiledHeader, r *stdHttp.Request) bool {
+	for _, ch := range chs {
+		if val := r.Header.Get(ch.Name); len(val) > 0 {
+			if ch.Regex != nil {
+				if ok := ch.Regex.MatchString(val); !ok {
+					return false
+				}
+				continue
+			}
+
+			if !slices.Contains(ch.Values, val) {
+				return false
+			}
 		} else {
-			key = getTrieKey(method, r.Match.Path, isPrefix)
+			return false
 		}
-		_, _ = rm.activeConfig.RouteTrie.Remove(key)
 	}
+	return true
 }
diff --git a/pkg/common/util/stringutil/stringutil.go b/pkg/common/util/stringutil/stringutil.go
index 23ba5eaf..405d0dec 100644
--- a/pkg/common/util/stringutil/stringutil.go
+++ b/pkg/common/util/stringutil/stringutil.go
@@ -59,32 +59,53 @@ func IsPathVariableOrWildcard(key string) bool {
 
 // IsWildcard return if is *
 func IsWildcard(key string) bool {
-	return key == "*"
+	return key == constant.HeaderValueAll
 }
 
+// IsMatchAll return if is **
 func IsMatchAll(key string) bool {
-	return key == "**"
+	return key == constant.HeaderValueAllLevels
 }
 
 func GetTrieKey(method string, path string) string {
+	// "http://localhost:8882/api/v1/test-dubbo/user?name=tc/"
 	ret := ""
-	//"http://localhost:8882/api/v1/test-dubbo/user?name=tc"
+
 	if strings.Contains(path, constant.ProtocolSlash) {
 		path = path[strings.Index(path, constant.ProtocolSlash)+len(constant.ProtocolSlash):]
 		path = path[strings.Index(path, constant.PathSlash)+1:]
 	}
+	// "api/v1/test-dubbo/user?name=tc/"
+
 	if strings.HasPrefix(path, constant.PathSlash) {
 		ret = method + path
 	} else {
 		ret = method + constant.PathSlash + path
 	}
+	// "METHOD/api/v1/test-dubbo/user?name=tc/"
+
 	if strings.HasSuffix(ret, constant.PathSlash) {
 		ret = ret[0 : len(ret)-1]
 	}
+	// "METHOD/api/v1/test-dubbo/user?name=tc"
+
 	ret = strings.Split(ret, "?")[0]
+	// "METHOD/api/v1/test-dubbo/user"
+
 	return ret
 }
 
+func GetTrieKeyWithPrefix(method, path, prefix string, isPrefix bool) string {
+	if isPrefix {
+		if prefix != "" && prefix[len(prefix)-1] != '/' {
+			prefix += constant.PathSlash
+		}
+		prefix += constant.HeaderValueAllLevels
+		return GetTrieKey(method, prefix)
+	}
+	return GetTrieKey(method, path)
+}
+
 func GetIPAndPort(address string) ([]*net.TCPAddr, error) {
 	if len(address) <= 0 {
 		return nil, errors.Errorf("invalid address, %s", address)
diff --git a/pkg/config/config_load.go b/pkg/config/config_load.go
index e45913ca..9e0d519a 100644
--- a/pkg/config/config_load.go
+++ b/pkg/config/config_load.go
@@ -144,7 +144,7 @@ func GetHttpConfig(cfg *model.Bootstrap) (err error) {
 						logger.Error(err)
 					}
 					cfg.StaticResources.Listeners[i].Config = *hc
-					//cfg.StaticResources.Listeners[i].FilterChains
+					// cfg.StaticResources.Listeners[i].FilterChains
 				}
 			}
 		}
@@ -212,7 +212,7 @@ func (m *ConfigManager) LoadBootConfig(path string) *model.Bootstrap {
 	err := m.check()
 
 	if err != nil {
-		logger.Errorf("[Pixiu-Config] check bootstrap config fail : %s", err.Error())
+		logger.Errorf("check bootstrap config fail : %s", err.Error())
 		panic(err)
 	}
 
diff --git a/pkg/model/router.go b/pkg/model/router.go
index d3e7fadb..ff4f6a9e 100644
--- a/pkg/model/router.go
+++ b/pkg/model/router.go
@@ -97,7 +97,7 @@ func (rc *RouteConfiguration) Route(req *stdHttp.Request) (*RouteAction, error)
 	return rc.RouteByPathAndMethod(req.URL.Path, req.Method)
 }
 
-// MatchHeader used when there's only headers to match
+// MatchHeader used when there are only headers to match
 func (rm *RouterMatch) MatchHeader(req *stdHttp.Request) bool {
 	if len(rm.Methods) > 0 {
 		for _, method := range rm.Methods {
@@ -146,7 +146,12 @@ func (hm *HeaderMatcher) SetValueRegex(regex string) error {
 func (r *Router) String() string {
 	var builder strings.Builder
 	builder.WriteString("[" + strings.Join(r.Match.Methods, ",") + "] ")
-	if r.Match.Prefix != "" {
+	if r.Match.Path == "" && r.Match.Prefix == "" && len(r.Match.Headers) > 0 {
+		builder.WriteString("headers ")
+		for _, h := range r.Match.Headers {
+			builder.WriteString(h.Name + "=" + strings.Join(h.Values, "|"))
+		}
+	} else if r.Match.Prefix != "" {
 		builder.WriteString("prefix " + r.Match.Prefix)
 	} else {
 		builder.WriteString("path " + r.Match.Path)
diff --git a/pkg/model/router_snapshot.go b/pkg/model/router_snapshot.go
new file mode 100644
index 00000000..bae890cd
--- /dev/null
+++ b/pkg/model/router_snapshot.go
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package model
+
+import (
+	"regexp"
+	"sync"
+)
+
+import (
+	"github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
+	"github.com/apache/dubbo-go-pixiu/pkg/common/util/stringutil"
+	"github.com/apache/dubbo-go-pixiu/pkg/logger"
+)
+
+var (
+	constMethods = []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"}
+)
+
+// RouteSnapshot Read-only snapshot for routing
+type RouteSnapshot struct {
+	// multi-trie for each method, built once and read-only
+	MethodTries map[string]*trie.Trie
+
+	// precompiled regex for header-only routes
+	HeaderOnly []HeaderRoute
+}
+
+type HeaderRoute struct {
+	Methods []string
+	Headers []CompiledHeader
+	Action  RouteAction
+}
+
+type CompiledHeader struct {
+	Name   string
+	Regex  *regexp.Regexp
+	Values []string
+}
+
+func MethodAllowed(methods []string, m string) bool {
+	if len(methods) == 0 {
+		return true
+	}
+	for _, x := range methods {
+		if x == m {
+			return true
+		}
+	}
+	return false
+}
+
+var regexCache sync.Map // map[string]*regexp.Regexp
+
+func getRegexpWithCache(pat string) *regexp.Regexp {
+	if v, ok := regexCache.Load(pat); ok {
+		return v.(*regexp.Regexp)
+	}
+	// Compile fail return nil (caller will ignore this regex)
+	re, err := regexp.Compile(pat)
+	if err != nil {
+		return nil
+	}
+	if v, ok := regexCache.LoadOrStore(pat, re); ok {
+		return v.(*regexp.Regexp)
+	}
+	return re
+}
+
+// compiledHeaderSlicePool is a pool for temporary []CompiledHeader slices during snapshot building
+var compiledHeaderSlicePool = sync.Pool{
+	New: func() any {
+		s := make([]CompiledHeader, 0, 4) // start with small capacity, grow as needed
+		return &s
+	},
+}
+
+func ToSnapshot(routes []*Router) *RouteSnapshot {
+	s := &RouteSnapshot{
+		MethodTries: make(map[string]*trie.Trie, 8),
+	}
+
+	// pre-scan header-only routes count
+	headerOnlyCount := 0
+	for _, r := range routes {
+		if r.Match.Path == "" && r.Match.Prefix == "" && len(r.Match.Headers) > 0 {
+			headerOnlyCount++
+		}
+	}
+
+	if headerOnlyCount > 0 {
+		s.HeaderOnly = make([]HeaderRoute, 0, headerOnlyCount)
+	}
+
+	// part to get or create trie for a method
+	getTrie := func(m string) *trie.Trie {
+		if t := s.MethodTries[m]; t != nil {
+			return t
+		}
+		nt := trie.NewTrie()
+		s.MethodTries[m] = &nt
+		return &nt
+	}
+
+	for _, r := range routes {
+		// A) header-only：with Headers, without Path / Prefix
+		if r.Match.Path == "" && r.Match.Prefix == "" && len(r.Match.Headers) > 0 {
+			hr := HeaderRoute{
+				Methods: r.Match.Methods,
+				Action:  r.Route,
+			}
+
+			// use temporary slice from pool to build compiled headers
+			chPtr := compiledHeaderSlicePool.Get().(*[]CompiledHeader)
+			ch := (*chPtr)[:0] // reset
+
+			for _, h := range r.Match.Headers {
+				c := CompiledHeader{Name: h.Name}
+				if h.Regex {
+					// 1) the model already has compiled regex (if any) → use it directly
+					if h.valueRE != nil {
+						c.Regex = h.valueRE
+					} else if len(h.Values) > 0 && h.Values[0] != "" {
+						// 2) else use global cache/compile (cross-snapshot reuse)
+						if re := getRegexpWithCache(h.Values[0]); re != nil {
+							c.Regex = re
+						} else {
+							// invalid regex → skip this header matcher
+							logger.Errorf("Header regex compiled fail for %v", h.Values[0])
+							continue
+						}
+					}
+				} else {
+					// not regex → copy values directly (if any)
+					if len(h.Values) > 0 {
+						// direct assignment is ok here (string slice)
+						c.Values = append(c.Values, h.Values...)
+					}
+				}
+				ch = append(ch, c)
+			}
+
+			// move the temporary slice content to snapshot (ownership transferred)
+			hr.Headers = make([]CompiledHeader, len(ch))
+			copy(hr.Headers, ch)
+
+			// reset and put back the temporary slice to pool
+			*chPtr = (*chPtr)[:0]
+			compiledHeaderSlicePool.Put(chPtr)
+
+			s.HeaderOnly = append(s.HeaderOnly, hr)
+			continue
+		}
+
+		// B) Trie
+		methods := r.Match.Methods
+		if len(methods) == 0 {
+			methods = constMethods
+		}
+		for _, m := range methods {
+			t := getTrie(m)
+			t.Put(stringutil.GetTrieKeyWithPrefix(m, r.Match.Path, r.Match.Prefix, r.Match.Prefix != ""), r.Route)
+		}
+	}
+	return s
+}



Test patch:
diff --git a/pkg/common/http/manager_test.go b/pkg/common/http/manager_test.go
index 33080f38..69cb4458 100644
--- a/pkg/common/http/manager_test.go
+++ b/pkg/common/http/manager_test.go
@@ -39,7 +39,6 @@ import (
 	"github.com/apache/dubbo-go-pixiu/pkg/common/constant"
 	"github.com/apache/dubbo-go-pixiu/pkg/common/extension/filter"
 	commonmock "github.com/apache/dubbo-go-pixiu/pkg/common/mock"
-	"github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
 	contexthttp "github.com/apache/dubbo-go-pixiu/pkg/context/http"
 	"github.com/apache/dubbo-go-pixiu/pkg/context/mock"
 	"github.com/apache/dubbo-go-pixiu/pkg/logger"
@@ -121,10 +120,19 @@ func TestCreateHttpConnectionManager(t *testing.T) {
 
 	hcmc := model.HttpConnectionManagerConfig{
 		RouteConfig: model.RouteConfiguration{
-			RouteTrie: trie.NewTrieWithDefault("POST/api/v1/**", model.RouteAction{
-				Cluster:                     "test_dubbo",
-				ClusterNotFoundResponseCode: 505,
-			}),
+			Routes: []*model.Router{
+				{
+					ID: "1",
+					Match: model.RouterMatch{
+						Path:    "/api/v1/**",
+						Methods: []string{"POST"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     "test_dubbo",
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+			},
 			Dynamic: false,
 		},
 		HTTPFilters: []*model.HTTPFilter{
@@ -157,9 +165,20 @@ func TestCreateHttpConnectionManager(t *testing.T) {
 func TestStreamingResponse(t *testing.T) {
 	hcmc := model.HttpConnectionManagerConfig{
 		RouteConfig: model.RouteConfiguration{
-			RouteTrie: trie.NewTrieWithDefault("GET/api/sse", model.RouteAction{
-				Cluster: "mock_stream_cluster",
-			}),
+			Routes: []*model.Router{
+				{
+					ID: "1",
+					Match: model.RouterMatch{
+						Path:    "/api/sse",
+						Methods: []string{"GET"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     "mock_stream_cluster",
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+			},
+			Dynamic: false,
 		},
 		HTTPFilters: []*model.HTTPFilter{
 			{
@@ -360,9 +379,20 @@ func TestStreamableHTTPResponse(t *testing.T) {
 func testStreamableResponse(t *testing.T, contentType string) {
 	hcmc := model.HttpConnectionManagerConfig{
 		RouteConfig: model.RouteConfiguration{
-			RouteTrie: trie.NewTrieWithDefault("GET/api/stream", model.RouteAction{
-				Cluster: "mock_stream_cluster",
-			}),
+			Routes: []*model.Router{
+				{
+					ID: "1",
+					Match: model.RouterMatch{
+						Path:    "/api/stream",
+						Methods: []string{"GET"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     "mock_stream_cluster",
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+			},
+			Dynamic: false,
 		},
 		HTTPFilters: []*model.HTTPFilter{
 			{
diff --git a/pkg/common/router/router_bench_test.go b/pkg/common/router/router_bench_test.go
new file mode 100644
index 00000000..e44599d8
--- /dev/null
+++ b/pkg/common/router/router_bench_test.go
@@ -0,0 +1,504 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package router
+
+import (
+	"math/rand"
+	stdHttp "net/http"
+	"reflect"
+	"strconv"
+	"testing"
+	"time"
+)
+
+import (
+	oldrouter "github.com/apache/dubbo-go-pixiu/pkg/common/router/mock"
+	"github.com/apache/dubbo-go-pixiu/pkg/context/http"
+	"github.com/apache/dubbo-go-pixiu/pkg/model"
+)
+
+/*
+==============================
+this is the benchmark for router
+contrast oldrouter and newrouter
+oldrouter: "github.com/apache/dubbo-go-pixiu/pkg/common/router/mock"
+newrouter: "github.com/apache/dubbo-go-pixiu/pkg/common/router"
+==============================
+*/
+type benchShape struct {
+	NRoutes         int     // router number
+	PrefixRatio     float64 // prefix router ratio（others are accurate path）
+	HeaderOnlyRatio float64 // header only router ratio（without path/prefix）
+	Methods         []string
+}
+
+func buildOldCoordinator(routes []*model.Router) *oldrouter.RouterCoordinator {
+	cfg := &model.RouteConfiguration{
+		Routes:  routes,
+		Dynamic: false,
+	}
+	return oldrouter.CreateRouterCoordinator(cfg)
+}
+
+func genRoutes(sh benchShape) []*model.Router {
+	routes := make([]*model.Router, 0, sh.NRoutes)
+	if len(sh.Methods) == 0 {
+		sh.Methods = []string{"GET", "POST"}
+	}
+	nHeader := int(float64(sh.NRoutes) * sh.HeaderOnlyRatio)
+	nPrefix := int(float64(sh.NRoutes-nHeader) * sh.PrefixRatio)
+	nPath := sh.NRoutes - nHeader - nPrefix
+
+	// 1) Header-only
+	for i := 0; i < nHeader; i++ {
+		id := "hdr-" + strconv.Itoa(i)
+		r := &model.Router{
+			ID: id,
+			Match: model.RouterMatch{
+				Methods: sh.Methods,
+				Headers: []model.HeaderMatcher{
+					{Name: "X-Env", Values: []string{"prod"}, Regex: false},
+				},
+			},
+			Route: model.RouteAction{Cluster: "c-h-" + id},
+		}
+		routes = append(routes, r)
+	}
+	// 2) Prefix routes
+	for i := 0; i < nPrefix; i++ {
+		id := "pre-" + strconv.Itoa(i)
+		p := "/api/v1/service" + strconv.Itoa(i%50) + "/"
+		r := &model.Router{
+			ID: id,
+			Match: model.RouterMatch{
+				Methods: sh.Methods,
+				Prefix:  p,
+			},
+			Route: model.RouteAction{Cluster: "c-p-" + id},
+		}
+		routes = append(routes, r)
+	}
+	// 3) Exact path
+	for i := 0; i < nPath; i++ {
+		id := "pth-" + strconv.Itoa(i)
+		pp := "/api/v1/item/" + strconv.Itoa(i)
+		r := &model.Router{
+			ID: id,
+			Match: model.RouterMatch{
+				Methods: sh.Methods,
+				Path:    pp,
+			},
+			Route: model.RouteAction{Cluster: "c-x-" + id},
+		}
+		routes = append(routes, r)
+	}
+	return routes
+}
+
+func buildNewCoordinator(routes []*model.Router) *RouterCoordinator {
+	cfg := &model.RouteConfiguration{
+		Routes:  routes,
+		Dynamic: false,
+	}
+	return CreateRouterCoordinator(cfg)
+}
+
+func buildDelta(base []*model.Router, seed int64) []*model.Router {
+	cp := make([]*model.Router, len(base))
+	copy(cp, base)
+	rnd := rand.New(rand.NewSource(seed))
+	k := len(cp) / 100 // 1%
+	out := make([]*model.Router, 0, k)
+	for i := 0; i < k; i++ {
+		idx := rnd.Intn(len(cp)) // NOSONAR
+		old := cp[idx]
+		newPath := "/api/v1/item/" + strconv.Itoa(rnd.Intn(100000)) // NOSONAR
+		nr := &model.Router{
+			ID: old.ID,
+			Match: model.RouterMatch{
+				Methods: old.Match.Methods,
+				Path:    newPath,
+				Headers: old.Match.Headers,
+			},
+			Route: old.Route,
+		}
+		out = append(out, nr)
+	}
+	return out
+}
+
+func genRequests(n int) []*stdHttp.Request {
+	reqs := make([]*stdHttp.Request, 0, n)
+	methods := []string{"GET", "POST"}
+	for i := 0; i < n; i++ {
+		var path string
+		switch i % 3 {
+		case 0:
+			path = "/api/v1/item/" + strconv.Itoa(i%10000)
+		case 1:
+			path = "/api/v1/service" + strconv.Itoa(i%50) + "/foo/bar"
+		default:
+			path = "/unknown/" + strconv.Itoa(i)
+		}
+		req, _ := stdHttp.NewRequest(methods[i%len(methods)], path, nil)
+		if i%5 == 0 { // trigger header-only route
+			req.Header.Set("X-Env", "prod")
+		}
+		reqs = append(reqs, req)
+	}
+	return reqs
+}
+
+// helper: assert Route behavior of old/new is the same on a set of requests
+func assertRouteSame(b testing.TB, oldc *oldrouter.RouterCoordinator, newc *RouterCoordinator, reqs []*stdHttp.Request) {
+	b.Helper()
+	for i, r := range reqs {
+		ctxOld := http.HttpContext{Request: r}
+		ctxNew := http.HttpContext{Request: r}
+
+		oldRes, oldErr := oldc.Route(&ctxOld)
+		newRes, newErr := newc.Route(&ctxNew)
+
+		if (oldErr != nil && newErr == nil) || (oldErr == nil && newErr != nil) {
+			b.Fatalf("route error text mismatch on #%d path=%s method=%s: oldErr=%v newErr=%v",
+				i, r.URL.Path, r.Method, oldErr, newErr)
+		}
+		if !reflect.DeepEqual(oldRes, newRes) {
+			b.Fatalf("route result mismatch on #%d path=%s method=%s: old=%#v new=%#v",
+				i, r.URL.Path, r.Method, oldRes, newRes)
+		}
+	}
+}
+
+// helper: assert RouteByPathAndName behavior is the same
+func assertRouteByPathAndNameSame(b testing.TB, oldc *oldrouter.RouterCoordinator, newc *RouterCoordinator, paths []string, method string) {
+	b.Helper()
+	for i, p := range paths {
+		oldRes, oldErr := oldc.RouteByPathAndName(p, method)
+		newRes, newErr := newc.RouteByPathAndName(p, method)
+
+		if (oldErr != nil && newErr == nil) || (oldErr == nil && newErr != nil) {
+			b.Fatalf("route error text mismatch on #%d path=%s method=%s: oldRes=%v oldErr=%v  newRes=%v newErr=%v",
+				i, p, method, oldRes, oldErr, newRes, newErr)
+		}
+		if !reflect.DeepEqual(oldRes, newRes) {
+			b.Fatalf("RouteByPathAndName result mismatch on #%d path=%s method=%s: old=%#v new=%#v",
+				i, p, method, oldRes, newRes)
+		}
+	}
+}
+
+// ============= Bench 1：read throughput (one goroutine) =============
+
+func BenchmarkRouteReadThroughput(b *testing.B) {
+	shape := benchShape{NRoutes: 30000, PrefixRatio: 0.4, HeaderOnlyRatio: 0.1, Methods: []string{"GET", "POST"}}
+
+	oldRoutes := genRoutes(shape)
+	newRoutes := genRoutes(shape)
+	reqs := genRequests(4096)
+
+	oldc := buildOldCoordinator(oldRoutes)
+	newc := buildNewCoordinator(newRoutes)
+
+	assertRouteSame(b, oldc, newc, reqs)
+
+	b.Run("old/locked-read-30k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			r := reqs[i%len(reqs)]
+			httpContext := http.HttpContext{
+				Request: r,
+			}
+			_, _ = oldc.Route(&httpContext)
+		}
+	})
+
+	b.Run("new/rcu-read-30k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			r := reqs[i%len(reqs)]
+			httpContext := http.HttpContext{
+				Request: r,
+			}
+			_, _ = newc.Route(&httpContext)
+		}
+	})
+}
+
+// ============= Bench 2：read throughput (parallel) =============
+
+func BenchmarkRouteReadParallel(b *testing.B) {
+	shape := benchShape{NRoutes: 30000, PrefixRatio: 0.4, HeaderOnlyRatio: 0.1, Methods: []string{"GET", "POST"}}
+
+	oldRoutes := genRoutes(shape)
+	newRoutes := genRoutes(shape)
+	reqs := genRequests(8192)
+
+	oldc := buildOldCoordinator(oldRoutes)
+	newc := buildNewCoordinator(newRoutes)
+
+	assertRouteSame(b, oldc, newc, reqs)
+
+	b.Run("old/parallel-30k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.SetParallelism(40)
+		b.ResetTimer()
+		b.RunParallel(func(pb *testing.PB) {
+			i := rand.Int() // NOSONAR
+			for pb.Next() {
+				r := reqs[i%len(reqs)]
+				httpContext := http.HttpContext{
+					Request: r,
+				}
+				_, _ = oldc.Route(&httpContext)
+				i++
+			}
+		})
+	})
+
+	b.Run("new/parallel-30k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.SetParallelism(40)
+		b.ResetTimer()
+		b.RunParallel(func(pb *testing.PB) {
+			i := rand.Int() // NOSONAR
+			for pb.Next() {
+				r := reqs[i%len(reqs)]
+				httpContext := http.HttpContext{
+					Request: r,
+				}
+				_, _ = newc.Route(&httpContext)
+				i++
+			}
+		})
+	})
+}
+
+// ============= Bench 3：read and write（1% write） =============
+
+func BenchmarkReloadLatency(b *testing.B) {
+	shape := benchShape{NRoutes: 30000, PrefixRatio: 0.4, HeaderOnlyRatio: 0.1, Methods: []string{"GET", "POST"}}
+	base := genRoutes(shape)
+
+	oldc := buildOldCoordinator(base)
+	newc := buildNewCoordinator(base)
+
+	{
+		checkOld := buildOldCoordinator(base)
+		checkNew := buildNewCoordinator(base)
+		deltaOld := buildDelta(base, 1)
+		for i := range deltaOld {
+			checkOld.OnAddRouter(deltaOld[i])
+			checkNew.OnAddRouter(deltaOld[i])
+		}
+		reqs := genRequests(1024)
+		time.Sleep(100 * time.Millisecond)
+		assertRouteSame(b, checkOld, checkNew, reqs)
+	}
+
+	b.Run("old/reload-1percent-30k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			for _, r := range buildDelta(base, int64(i)) {
+				oldc.OnAddRouter(r)
+			}
+		}
+	})
+
+	b.Run("new/reload-1percent-30k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			for _, r := range buildDelta(base, int64(i)) {
+				newc.OnAddRouter(r)
+			}
+		}
+	})
+}
+
+func BenchmarkRoute100kReadThroughput(b *testing.B) {
+	shape := benchShape{
+		NRoutes:         100_000,
+		PrefixRatio:     0.4,
+		HeaderOnlyRatio: 0.1,
+		Methods:         []string{"GET", "POST"},
+	}
+	reqs := genRequests(16_384)
+
+	oldc := buildOldCoordinator(genRoutes(shape))
+	newc := buildNewCoordinator(genRoutes(shape))
+
+	assertRouteSame(b, oldc, newc, reqs)
+
+	b.Run("old/locked-read-100k", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			r := reqs[i%len(reqs)]
+			httpContext := http.HttpContext{
+				Request: r,
+			}
+			_, _ = oldc.Route(&httpContext)
+		}
+	})
+	b.Run("new/rcu-read-100k", func(b *testing.B) {
+		b.ReportAllocs()
+		for i := 0; i < b.N; i++ {
+			r := reqs[i%len(reqs)]
+			httpContext := http.HttpContext{
+				Request: r,
+			}
+			_, _ = newc.Route(&httpContext)
+		}
+	})
+}
+
+func BenchmarkRoute100kReadParallel(b *testing.B) {
+	shape := benchShape{
+		NRoutes:         100_000,
+		PrefixRatio:     0.4,
+		HeaderOnlyRatio: 0.1,
+		Methods:         []string{"GET", "POST"},
+	}
+	reqs := genRequests(32_768)
+
+	oldc := buildOldCoordinator(genRoutes(shape))
+	newc := buildNewCoordinator(genRoutes(shape))
+
+	assertRouteSame(b, oldc, newc, reqs)
+
+	b.Run("old/parallel-100k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.SetParallelism(4)
+		b.RunParallel(func(pb *testing.PB) {
+			i := 0
+			for pb.Next() {
+				r := reqs[i&(len(reqs)-1)]
+				httpContext := http.HttpContext{
+					Request: r,
+				}
+				_, _ = oldc.Route(&httpContext)
+				i++
+			}
+		})
+	})
+	b.Run("new/parallel-100k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.SetParallelism(4)
+		b.RunParallel(func(pb *testing.PB) {
+			i := 0
+			for pb.Next() {
+				r := reqs[i&(len(reqs)-1)]
+				httpContext := http.HttpContext{
+					Request: r,
+				}
+				_, _ = newc.Route(&httpContext)
+				i++
+			}
+		})
+	})
+}
+
+func BenchmarkReload100kLatency1Percent(b *testing.B) {
+	shape := benchShape{
+		NRoutes:         100_000,
+		PrefixRatio:     0.4,
+		HeaderOnlyRatio: 0.1,
+		Methods:         []string{"GET", "POST"},
+	}
+	base := genRoutes(shape)
+
+	oldc := buildOldCoordinator(genRoutes(shape))
+	newc := buildNewCoordinator(genRoutes(shape))
+
+	{
+		checkOld := buildOldCoordinator(base)
+		checkNew := buildNewCoordinator(base)
+		deltaOld := buildDelta(base, 1)
+		for i := range deltaOld {
+			checkOld.OnAddRouter(deltaOld[i])
+			checkNew.OnAddRouter(deltaOld[i])
+		}
+		reqs := genRequests(2048)
+		time.Sleep(100 * time.Millisecond)
+		assertRouteSame(b, checkOld, checkNew, reqs)
+	}
+
+	b.Run("old/reload-1percent-100k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			for _, r := range buildDelta(base, int64(i)) {
+				oldc.OnAddRouter(r)
+			}
+		}
+	})
+
+	b.Run("new/reload-1percent-100k", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			for _, r := range buildDelta(base, int64(i)) {
+				newc.OnAddRouter(r)
+			}
+		}
+	})
+}
+
+// ============= Bench 4：RouteByPathAndName（API behavior must same） =============
+
+func BenchmarkRouteByPathAndName(b *testing.B) {
+	shape := benchShape{
+		NRoutes:         20000,
+		PrefixRatio:     0.5,
+		HeaderOnlyRatio: 0.0,
+		Methods:         []string{"GET"},
+	}
+
+	oldc := buildOldCoordinator(genRoutes(shape))
+	newc := buildNewCoordinator(genRoutes(shape))
+
+	paths := []string{
+		"/api/v1/item/12345",
+		"/api/v1/service7/xxx/yyy",
+		"/no/match/path",
+	}
+	method := "GET"
+
+	assertRouteByPathAndNameSame(b, oldc, newc, paths, method)
+
+	b.Run("old/RouteByPathAndName", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			path := paths[i%len(paths)]
+			_, _ = oldc.RouteByPathAndName(path, method)
+		}
+	})
+
+	b.Run("new/RouteByPathAndName", func(b *testing.B) {
+		b.ReportAllocs()
+		b.ResetTimer()
+		for i := 0; i < b.N; i++ {
+			path := paths[i%len(paths)]
+			_, _ = newc.RouteByPathAndName(path, method)
+		}
+	})
+}
diff --git a/pkg/common/router/router_test.go b/pkg/common/router/router_test.go
index 46441514..f3063a00 100644
--- a/pkg/common/router/router_test.go
+++ b/pkg/common/router/router_test.go
@@ -19,7 +19,9 @@ package router
 
 import (
 	"bytes"
-	"net/http"
+	"math/rand"
+	stdHttp "net/http"
+	"strconv"
 	"testing"
 )
 
@@ -28,36 +30,24 @@ import (
 )
 
 import (
-	"github.com/apache/dubbo-go-pixiu/pkg/common/router/trie"
+	oldrouter "github.com/apache/dubbo-go-pixiu/pkg/common/router/mock"
+	"github.com/apache/dubbo-go-pixiu/pkg/context/http"
 	"github.com/apache/dubbo-go-pixiu/pkg/context/mock"
 	"github.com/apache/dubbo-go-pixiu/pkg/model"
 )
 
 func TestCreateRouterCoordinator(t *testing.T) {
-	hcmc := model.HttpConnectionManagerConfig{
-		RouteConfig: model.RouteConfiguration{
-			RouteTrie: trie.NewTrieWithDefault("POST/api/v1/**", model.RouteAction{
-				Cluster:                     "test_dubbo",
-				ClusterNotFoundResponseCode: 505,
-			}),
-			Dynamic: false,
-		},
-		HTTPFilters: []*model.HTTPFilter{
-			{
-				Name:   "test",
-				Config: nil,
-			},
-		},
-		ServerName:        "test_http_dubbo",
-		GenerateRequestID: false,
-		IdleTimeoutStr:    "100",
+	specs := []RouteSpec{
+		// exact
+		{ID: "test", Methods: []string{"POST"}, Path: "/api/v1/**", Cluster: "test_dubbo"},
 	}
 
-	r := CreateRouterCoordinator(&hcmc.RouteConfig)
-	request, err := http.NewRequest("POST", "http://www.dubbogopixiu.com/api/v1?name=tc", bytes.NewReader([]byte("{\"id\":\"12345\"}")))
+	coordinator := BuildNew(specs)
+
+	request, err := stdHttp.NewRequest("POST", "http://www.dubbogopixiu.com/api/v1?name=tc", bytes.NewReader([]byte("{\"id\":\"12345\"}")))
 	assert.NoError(t, err)
 	c := mock.GetMockHTTPContext(request)
-	a, err := r.Route(c)
+	a, err := coordinator.Route(c)
 	assert.NoError(t, err)
 	assert.Equal(t, a.Cluster, "test_dubbo")
 
@@ -71,13 +61,19 @@ func TestCreateRouterCoordinator(t *testing.T) {
 		},
 	}
 
-	r.OnAddRouter(router)
-	r.OnDeleteRouter(router)
+	coordinator.OnAddRouter(router)
+	coordinator.OnDeleteRouter(router)
 }
 
 func TestRoute(t *testing.T) {
 	const (
 		Cluster1 = "test-cluster-1"
+		Cluster2 = "test-cluster-2"
+		Cluster3 = "test-cluster-3"
+		Cluster4 = "test-cluster-4"
+		Cluster5 = "test-cluster-5"
+		Cluster6 = "test-cluster-6"
+		Cluster7 = "test-cluster-7"
 	)
 
 	hcmc := model.HttpConnectionManagerConfig{
@@ -89,16 +85,52 @@ func TestRoute(t *testing.T) {
 						Headers: []model.HeaderMatcher{
 							{
 								Name:   "A",
-								Values: []string{"1", "2", "3"},
+								Values: []string{"1", "2", "0"},
 							},
+						},
+						Methods: []string{"GET", "POST"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     Cluster1,
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+				{
+					ID: "2",
+					Match: model.RouterMatch{
+						Headers: []model.HeaderMatcher{
 							{
 								Name:   "A",
 								Values: []string{"3", "4", "5"},
 							},
+						},
+						Methods: []string{"GET", "POST"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     Cluster2,
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+				{
+					ID: "3",
+					Match: model.RouterMatch{
+						Headers: []model.HeaderMatcher{
 							{
 								Name:   "B",
 								Values: []string{"1"},
 							},
+						},
+						Methods: []string{"GET", "POST"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     Cluster3,
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+				{
+					ID: "4",
+					Match: model.RouterMatch{
+						Headers: []model.HeaderMatcher{
 							{
 								Name:   "normal-regex",
 								Values: []string{"(k){2}"},
@@ -113,7 +145,65 @@ func TestRoute(t *testing.T) {
 						Methods: []string{"GET", "POST"},
 					},
 					Route: model.RouteAction{
-						Cluster:                     Cluster1,
+						Cluster:                     Cluster4,
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+				{
+					ID: "5",
+					Match: model.RouterMatch{
+						Headers: []model.HeaderMatcher{
+							{
+								Name:   "broken-regex",
+								Values: []string{"(t){2]]"},
+								Regex:  true,
+							},
+						},
+						Methods: []string{"GET", "POST"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     Cluster5,
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+				{
+					ID: "6",
+					Match: model.RouterMatch{
+						Headers: []model.HeaderMatcher{
+							{
+								Name:   "C",
+								Values: []string{"1", "2", "0"},
+							},
+							{
+								Name:   "D",
+								Values: []string{"3", "4", "5"},
+							},
+						},
+						Methods: []string{"GET", "POST"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     Cluster6,
+						ClusterNotFoundResponseCode: 505,
+					},
+				},
+				{
+					ID: "7",
+					Match: model.RouterMatch{
+						Headers: []model.HeaderMatcher{
+							{
+								Name:   "E",
+								Values: []string{"1", "2", "0"},
+							},
+							{
+								Name:   "normal-regex",
+								Values: []string{"(k){2}"},
+								Regex:  true,
+							},
+						},
+						Methods: []string{"GET", "POST"},
+					},
+					Route: model.RouteAction{
+						Cluster:                     Cluster7,
 						ClusterNotFoundResponseCode: 505,
 					},
 				},
@@ -144,7 +234,7 @@ func TestRoute(t *testing.T) {
 			Header: map[string]string{
 				"A": "1",
 			},
-			Expect: "test-cluster-1",
+			Expect: Cluster1,
 		},
 		{
 			Name: "one header matched",
@@ -152,7 +242,7 @@ func TestRoute(t *testing.T) {
 			Header: map[string]string{
 				"A": "3",
 			},
-			Expect: Cluster1,
+			Expect: Cluster2,
 		},
 		{
 			Name: "more header with one regex matched",
@@ -161,16 +251,16 @@ func TestRoute(t *testing.T) {
 				"A":            "5",
 				"normal-regex": "kkkk",
 			},
-			Expect: Cluster1,
+			Expect: Cluster2,
 		},
 		{
 			Name:   "one header but wrong method",
 			URL:    "/user",
 			Method: "PUT",
 			Header: map[string]string{
-				"A": "3",
+				"A": "0",
 			},
-			Expect: "route failed for PUT/user, no rules matched.",
+			Expect: "route failed for PUT/user, no rules matched",
 		},
 		{
 			Name: "one broken regex header",
@@ -178,14 +268,22 @@ func TestRoute(t *testing.T) {
 			Header: map[string]string{
 				"broken-regex": "tt",
 			},
-			Expect: "route failed for GET/user, no rules matched.",
+			Expect: "route failed for GET/user, no rules matched",
 		},
 		{
 			Name: "one matched header 2",
 			Header: map[string]string{
 				"B": "1",
 			},
-			Expect: Cluster1,
+			Expect: Cluster3,
+		},
+		{
+			Name:   "only header but wrong method",
+			Method: "DELETE",
+			Header: map[string]string{
+				"B": "1",
+			},
+			Expect: "route failed for DELETE, no rules matched",
 		},
 		{
 			Name:   "only header but wrong method",
@@ -193,7 +291,25 @@ func TestRoute(t *testing.T) {
 			Header: map[string]string{
 				"B": "1",
 			},
-			Expect: "route failed for DELETE, no rules matched.",
+			Expect: "route failed for DELETE, no rules matched",
+		},
+		{
+			Name: "regex AND normal",
+			URL:  "/user",
+			Header: map[string]string{
+				"E":            "0",
+				"normal-regex": "kk",
+			},
+			Expect: Cluster7,
+		},
+		{
+			Name: "normal AND normal",
+			URL:  "/user",
+			Header: map[string]string{
+				"C": "1",
+				"D": "3",
+			},
+			Expect: Cluster6,
 		},
 	}
 
@@ -205,7 +321,7 @@ func TestRoute(t *testing.T) {
 			if len(tc.Method) > 0 {
 				method = tc.Method
 			}
-			request, err := http.NewRequest(method, tc.URL, nil)
+			request, err := stdHttp.NewRequest(method, tc.URL, nil)
 			assert.NoError(t, err)
 
 			if tc.Header != nil {
@@ -224,3 +340,619 @@ func TestRoute(t *testing.T) {
 		})
 	}
 }
+
+/* ==============================
+   below are parity test between old and new router
+   ============================== */
+
+type HeaderSpec struct {
+	Name   string
+	Values []string
+	Regex  bool
+}
+
+type RouteSpec struct {
+	ID      string
+	Methods []string
+	Path    string
+	Prefix  string
+	Headers []HeaderSpec
+	Cluster string
+}
+
+func (s RouteSpec) toRouter() *model.Router {
+	h := make([]model.HeaderMatcher, 0, len(s.Headers))
+	for _, x := range s.Headers {
+		h = append(h, model.HeaderMatcher{Name: x.Name, Values: append([]string(nil), x.Values...), Regex: x.Regex})
+	}
+	return &model.Router{
+		ID: s.ID,
+		Match: model.RouterMatch{
+			Methods: append([]string(nil), s.Methods...),
+			Path:    s.Path,
+			Prefix:  s.Prefix,
+			Headers: h,
+		},
+		Route: model.RouteAction{Cluster: s.Cluster},
+	}
+}
+
+func buildOld(specs []RouteSpec) *oldrouter.RouterCoordinator {
+	rs := make([]*model.Router, 0, len(specs))
+	for _, s := range specs {
+		rs = append(rs, s.toRouter())
+	}
+	cfg := &model.RouteConfiguration{Routes: rs, Dynamic: false}
+	return oldrouter.CreateRouterCoordinator(cfg)
+}
+
+func BuildNew(specs []RouteSpec) *RouterCoordinator {
+	rs := make([]*model.Router, 0, len(specs))
+	for _, s := range specs {
+		rs = append(rs, s.toRouter())
+	}
+	cfg := &model.RouteConfiguration{Routes: rs, Dynamic: false}
+	return CreateRouterCoordinator(cfg)
+}
+
+type res struct {
+	ok      bool
+	cluster string
+	err     string
+}
+
+func call(cOld *oldrouter.RouterCoordinator, cNew *RouterCoordinator, method, path string, hdr map[string]string) (res, res) {
+	req, _ := stdHttp.NewRequest(method, path, nil)
+	httpContext := http.HttpContext{
+		Request: req,
+	}
+	for k, v := range hdr {
+		req.Header.Set(k, v)
+	}
+	oa, oe := cOld.Route(&httpContext)
+	na, ne := cNew.Route(&httpContext)
+
+	or := res{}
+	if oe != nil || oa == nil {
+		if oe != nil {
+			or.err = oe.Error()
+		}
+	} else {
+		or.ok = true
+		or.cluster = oa.Cluster
+	}
+
+	nr := res{}
+	if ne != nil || na == nil {
+		if ne != nil {
+			nr.err = ne.Error()
+		}
+	} else {
+		nr.ok = true
+		nr.cluster = na.Cluster
+	}
+	return or, nr
+}
+
+func assertSame(t *testing.T, oldc *oldrouter.RouterCoordinator, newc *RouterCoordinator,
+	method, path string, hdr map[string]string, wantOK bool, wantCluster string) {
+
+	ro, rn := call(oldc, newc, method, path, hdr)
+	if ro.ok != rn.ok || ro.cluster != rn.cluster {
+		t.Fatalf("mismatch: %s %s hdr=%v\n old={ok:%v cluster:%q err:%q}\n new={ok:%v cluster:%q err:%q}",
+			method, path, hdr, ro.ok, ro.cluster, ro.err, rn.ok, rn.cluster, rn.err)
+	}
+	if ro.ok != wantOK || rn.ok != wantOK {
+		t.Fatalf("ok mismatch: %s %s hdr=%v wantOK=%v oldOK=%v newOK=%v", method, path, hdr, wantOK, ro.ok, rn.ok)
+	}
+	if wantOK && wantCluster != "" && (ro.cluster != wantCluster || rn.cluster != wantCluster) {
+		t.Fatalf("cluster mismatch: %s %s hdr=%v want=%q old=%q new=%q", method, path, hdr, wantCluster, ro.cluster, rn.cluster)
+	}
+}
+
+type varSyntax struct {
+	simplePattern func(seg string) string         // /users/:id
+	digitsPattern func(seg string) (string, bool) // /users/:id(\d+)
+	multiPattern  func(a, b string) string        // /shops/:a/orders/:b
+}
+
+func colonSyntax() varSyntax {
+	return varSyntax{
+		simplePattern: func(seg string) string {
+			return "/users/:" + seg
+		},
+		digitsPattern: func(seg string) (string, bool) {
+			return "/users/:" + seg + "(\\d+)", true
+		},
+		multiPattern: func(a, b string) string {
+			return "/shops/:" + a + "/orders/:" + b
+		},
+	}
+}
+
+/* ==============================
+   test cases (var/regex/priority/header/)
+   ============================== */
+
+func TestParitySimpleCases(t *testing.T) {
+	syntax = colonSyntax()
+
+	specs := []RouteSpec{
+		// exact
+		{ID: "exact", Methods: []string{"GET"}, Path: "/api/v1/item/100", Cluster: "c-exact"},
+		// prefix（/**）
+		{ID: "pre", Methods: []string{"GET"}, Prefix: "/api/v1/svc/", Cluster: "c-pre"},
+		// var
+		{ID: "var", Methods: []string{"GET"}, Path: syntax.simplePattern("id"), Cluster: "c-var"},
+		// multi
+		{ID: "multi", Methods: []string{"GET", "POST"}, Path: "/multi", Cluster: "c-multi"},
+		// Header regex
+		{ID: "hdr", Methods: []string{"GET"}, Headers: []HeaderSpec{{Name: "X-Env", Values: []string{"^prod|staging$"}, Regex: true}}, Cluster: "c-hdr"},
+	}
+
+	oldc := buildOld(specs)
+	newc := BuildNew(specs)
+
+	cases := []struct {
+		name    string
+		method  string
+		path    string
+		hdr     map[string]string
+		ok      bool
+		cluster string
+	}{
+		{"exact", "GET", "/api/v1/item/100", nil, true, "c-exact"},
+		{"prefix.deep", "GET", "/api/v1/svc/a/b", nil, true, "c-pre"},
+		{"var.hit", "GET", "/users/42", nil, true, "c-var"},
+		{"var.not_deeper", "GET", "/users/42/extra", nil, false, ""},
+		{"multi.get", "GET", "/multi", nil, true, "c-multi"},
+		{"multi.post", "POST", "/multi", nil, true, "c-multi"},
+		{"hdr.regex", "GET", "/whatever", map[string]string{"X-Env": "prod"}, true, "c-hdr"},
+		{"miss", "GET", "/no/match", nil, false, ""},
+	}
+
+	for _, tc := range cases {
+		t.Run(tc.name, func(t *testing.T) {
+			assertSame(t, oldc, newc, tc.method, tc.path, tc.hdr, tc.ok, tc.cluster)
+		})
+	}
+}
+
+func TestPrioritySpecificOverWildcard(t *testing.T) {
+	syntax = colonSyntax()
+
+	specs := []RouteSpec{
+		{ID: "wild", Methods: []string{"GET"}, Prefix: "/api/v1/**", Cluster: "c-wild"},
+		{ID: "spec", Methods: []string{"GET"}, Path: "/api/v1/test-dubbo/user/name/" + syntax.simplePattern("name")[len("/users/"):], Cluster: "c-spec"},
+		// equals to /api/v1/test-dubbo/user/name/:name
+	}
+	oldc := buildOld(specs)
+	newc := BuildNew(specs)
+
+	assertSame(t, oldc, newc, "GET",
+		"/api/v1/test-dubbo/user/name/yqxu", nil, true, "c-spec")
+}
+
+func TestPriorityDeeperWins(t *testing.T) {
+	specs := []RouteSpec{
+		{ID: "shallow", Methods: []string{"GET"}, Prefix: "/api/v1/", Cluster: "c-shallow"},
+		{ID: "deeper", Methods: []string{"GET"}, Prefix: "/api/v1/test-dubbo/", Cluster: "c-deeper"},
+	}
+	oldc := buildOld(specs)
+	newc := BuildNew(specs)
+
+	assertSame(t, oldc, newc, "GET",
+		"/api/v1/test-dubbo/user/name/abc", nil, true, "c-deeper")
+}
+
+func TestPrioritySingleStarOverDoubleStar(t *testing.T) {
+	// use var to express "/*"
+	syntax = colonSyntax()
+	specs := []RouteSpec{
+		{ID: "multi", Methods: []string{"GET"}, Prefix: "/api/", Cluster: "c-**"},
+		{ID: "single", Methods: []string{"GET"}, Path: "/api/" + syntax.simplePattern("seg")[len("/users/"):] + "/users", Cluster: "c-*"},
+		// equals to /api/:seg/users
+	}
+	oldc := buildOld(specs)
+	newc := BuildNew(specs)
+
+	assertSame(t, oldc, newc, "GET", "/api/v1/users", nil, true, "c-*")
+	assertSame(t, oldc, newc, "GET", "/api/v1/x/users", nil, true, "c-**")
+}
+
+func TestVariablesSingleAndMulti(t *testing.T) {
+	syntax = colonSyntax()
+	specs := []RouteSpec{
+		{ID: "one", Methods: []string{"GET"}, Path: syntax.simplePattern("id"), Cluster: "c-one"},
+		{ID: "two", Methods: []string{"GET"}, Path: syntax.multiPattern("shopId", "orderId"), Cluster: "c-two"},
+		{ID: "pre", Methods: []string{"GET"}, Prefix: "/shops/", Cluster: "c-pre"},
+	}
+	oldc := buildOld(specs)
+	newc := BuildNew(specs)
+
+	assertSame(t, oldc, newc, "GET", "/users/777", nil, true, "c-one")
+	assertSame(t, oldc, newc, "GET", syntax.multiPattern("12", "34"), nil, true, "c-two")
+	assertSame(t, oldc, newc, "GET", syntax.multiPattern("12", "34")+"/extra", nil, true, "c-pre")
+}
+
+func TestHeaderRegexWithRoutes(t *testing.T) {
+	specs := []RouteSpec{
+		{ID: "hdr", Methods: []string{"GET"}, Headers: []HeaderSpec{{Name: "X-Env", Values: []string{"^prod|staging$"}, Regex: true}}, Cluster: "c-hdr"},
+		{ID: "pre", Methods: []string{"GET"}, Prefix: "/api/", Cluster: "c-pre"},
+	}
+	oldc := buildOld(specs)
+	newc := BuildNew(specs)
+
+	assertSame(t, oldc, newc, "GET", "/whatever", map[string]string{"X-Env": "prod"}, true, "c-hdr")
+	assertSame(t, oldc, newc, "GET", "/api/foo", map[string]string{"X-Env": "dev"}, true, "c-pre")
+}
+
+/* ==============================
+   random data fuzz test
+   ============================== */
+
+func TestParityRandomized(t *testing.T) {
+	syntax = colonSyntax()
+	const (
+		nRoutes           = 20000
+		nRequests         = 10000
+		prefixRatio       = 0.40
+		headerRatio       = 0.10
+		seed        int64 = 20250929
+	)
+
+	specs := genRandomSpecsWithVars(syntax, nRoutes, prefixRatio, headerRatio, seed)
+	oldc := buildOld(specs)
+	newc := BuildNew(specs)
+
+	reqs := genRandomRequests(nRequests, seed+1)
+	for i, req := range reqs {
+		ro, rn := call(oldc, newc, req.Method, req.URL.Path, headerFromReq(req))
+		if ro.ok != rn.ok || ro.cluster != rn.cluster {
+			t.Fatalf("Randomized mismatch at #%d: %s %s old={ok:%v cluster:%q err:%q} new={ok:%v cluster:%q err:%q}",
+				i, req.Method, req.URL.Path, ro.ok, ro.cluster, ro.err, rn.ok, rn.cluster, rn.err)
+		}
+	}
+}
+
+func headerFromReq(r *stdHttp.Request) map[string]string {
+	if len(r.Header) == 0 {
+		return nil
+	}
+	out := make(map[string]string)
+	for k, vs := range r.Header {
+		if len(vs) > 0 {
+			out[k] = vs[0]
+		}
+	}
+	return out
+}
+
+/* ==============================
+   random data generation tools
+   ============================== */
+
+var syntax varSyntax
+
+func genRandomSpecsWithVars(s varSyntax, n int, prefixRatio, headerOnlyRatio float64, seed int64) []RouteSpec {
+	rnd := rand.New(rand.NewSource(seed))
+	out := make([]RouteSpec, 0, n)
+
+	nHeader := int(float64(n) * headerOnlyRatio)
+	nPrefix := int(float64(n-nHeader) * prefixRatio)
+	// preserve 20% for "variable path", the rest for exact path
+	nVars := int(float64(n) * 0.20)
+	nPath := n - nHeader - nPrefix - nVars
+	if nPath < 0 {
+		nPath = 0
+	}
+
+	// Header-only (regex + normal)
+	for i := 0; i < nHeader; i++ {
+		if i%5 == 0 {
+			out = append(out, RouteSpec{
+				ID:      "hdrx-" + strconv.Itoa(i),
+				Methods: []string{"GET", "POST"},
+				Headers: []HeaderSpec{{Name: "X-Trace", Values: []string{"^pixiu-[0-9a-f]{8}$"}, Regex: true}},
+				Cluster: "c-hx-" + strconv.Itoa(i),
+			})
+		} else {
+			out = append(out, RouteSpec{
+				ID:      "hdr-" + strconv.Itoa(i),
+				Methods: []string{"GET", "POST"},
+				Headers: []HeaderSpec{{Name: "X-Env", Values: []string{"prod"}, Regex: false}},
+				Cluster: "c-h-" + strconv.Itoa(i),
+			})
+		}
+	}
+
+	// Prefix
+	for i := 0; i < nPrefix; i++ {
+		base := "/api/v" + strconv.Itoa(1+rnd.Intn(3)) + "/svc" + strconv.Itoa(rnd.Intn(50)) + "/" // NOSONAR
+		out = append(out, RouteSpec{
+			ID:      "pre-" + strconv.Itoa(i),
+			Methods: []string{"GET", "POST"},
+			Prefix:  base,
+			Cluster: "c-p-" + strconv.Itoa(i),
+		})
+	}
+
+	// Variables
+	for i := 0; i < nVars; i++ {
+		if i%3 == 0 {
+			out = append(out, RouteSpec{
+				ID:      "var-" + strconv.Itoa(i),
+				Methods: []string{"GET"},
+				Path:    s.simplePattern("id"),
+				Cluster: "c-v-" + strconv.Itoa(i),
+			})
+		} else {
+			out = append(out, RouteSpec{
+				ID:      "var2-" + strconv.Itoa(i),
+				Methods: []string{"GET"},
+				Path:    s.multiPattern("a", "b"),
+				Cluster: "c-v2-" + strconv.Itoa(i),
+			})
+		}
+	}
+
+	// Exact Path
+	for i := 0; i < nPath; i++ {
+		out = append(out, RouteSpec{
+			ID:      "pth-" + strconv.Itoa(i),
+			Methods: []string{"GET"},
+			Path:    "/api/v1/item/" + strconv.Itoa(i),
+			Cluster: "c-x-" + strconv.Itoa(i),
+		})
+	}
+	return out
+}
+
+func genRandomRequests(n int, seed int64) []*stdHttp.Request {
+	rnd := rand.New(rand.NewSource(seed))
+	reqs := make([]*stdHttp.Request, 0, n)
+	methods := []string{"GET", "POST"}
+
+	for i := 0; i < n; i++ {
+		var path string
+		switch rnd.Intn(5) { // NOSONAR
+		case 0: // exact style
+			path = "/api/v1/item/" + strconv.Itoa(rnd.Intn(50000)) // NOSONAR
+		case 1: // prefix style
+			path = "/api/v" + strconv.Itoa(1+rnd.Intn(3)) + "/svc" + strconv.Itoa(rnd.Intn(50)) + "/foo/bar" // NOSONAR
+		case 2: // var
+			path = "/users/" + strconv.Itoa(1000+rnd.Intn(9000)) // NOSONAR
+		case 3: // var
+			path = "/shops/" + strconv.Itoa(rnd.Intn(100)) + "/orders/" + strconv.Itoa(rnd.Intn(1000)) // NOSONAR
+		default:
+			path = "/unknown/" + strconv.Itoa(rnd.Intn(100000)) // NOSONAR
+		}
+		req, _ := stdHttp.NewRequest(methods[rnd.Intn(len(methods))], path, nil) // NOSONAR
+		// header-only
+		switch rnd.Intn(7) { // NOSONAR
+		case 0:
+			req.Header.Set("X-Env", "prod")
+		case 1:
+			req.Header.Set("X-Trace", "pixiu-"+strconv.FormatInt(rnd.Int63()&0xffffffff, 16)) // NOSONAR
+		}
+		reqs = append(reqs, req)
+	}
+	return reqs
+}
+
+func newTestRouter(id, method, path, cluster string) *model.Router {
+	return &model.Router{
+		ID: id,
+		Match: model.RouterMatch{
+			Methods: []string{method},
+			Path:    path,
+		},
+		Route: model.RouteAction{
+			Cluster: cluster,
+		},
+	}
+}
+
+func mustRouteCluster(t *testing.T, rm *RouterCoordinator, method, path string, want string) {
+	t.Helper()
+	req, err := stdHttp.NewRequest(method, path, nil)
+	if err != nil {
+		t.Fatalf("failed to build request: %v", err)
+	}
+	ctx := http.HttpContext{Request: req}
+	act, err := rm.Route(&ctx)
+	if err != nil {
+		t.Fatalf("unexpected route error for %s %s: %v", method, path, err)
+	}
+	if act == nil {
+		t.Fatalf("route action is nil for %s %s", method, path)
+	}
+	if act.Cluster != want {
+		t.Fatalf("unexpected cluster for %s %s, want %q, got %q", method, path, want, act.Cluster)
+	}
+}
+
+func mustRouteNotFound(t *testing.T, rm *RouterCoordinator, method, path string) {
+	t.Helper()
+	req, err := stdHttp.NewRequest(method, path, nil)
+	if err != nil {
+		t.Fatalf("failed to build request: %v", err)
+	}
+	ctx := http.HttpContext{Request: req}
+	act, err := rm.Route(&ctx)
+	if err == nil {
+		t.Fatalf("expected error for %s %s, got nil (action=%#v)", method, path, act)
+	}
+}
+
+func mustRouteByPathAndNameCluster(t *testing.T, rm *RouterCoordinator, method, path string, want string) {
+	t.Helper()
+	act, err := rm.RouteByPathAndName(path, method)
+	if err != nil {
+		t.Fatalf("unexpected RouteByPathAndName error for %s %s: %v", method, path, err)
+	}
+	if act == nil {
+		t.Fatalf("route action is nil for %s %s", method, path)
+	}
+	if act.Cluster != want {
+		t.Fatalf("unexpected cluster for %s %s, want %q, got %q", method, path, want, act.Cluster)
+	}
+}
+
+func mustRouteByPathAndNameNotFound(t *testing.T, rm *RouterCoordinator, method, path string) {
+	t.Helper()
+	act, err := rm.RouteByPathAndName(path, method)
+	if err == nil {
+		t.Fatalf("expected RouteByPathAndName error for %s %s, got nil (action=%#v)", method, path, act)
+	}
+}
+
+func TestCreateRouterCoordinatorInitialSnapshot(t *testing.T) {
+	r1 := newTestRouter("r1", "GET", "/foo", "cluster-foo")
+	r2 := newTestRouter("r2", "GET", "/bar", "cluster-bar")
+
+	cfg := &model.RouteConfiguration{
+		Routes:  []*model.Router{r1, r2},
+		Dynamic: false,
+	}
+
+	rm := CreateRouterCoordinator(cfg)
+	rm.debounce = 0
+
+	mustRouteCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteCluster(t, rm, "GET", "/bar", "cluster-bar")
+
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/bar", "cluster-bar")
+}
+
+// TestRouterCoordinatorOnAddRouterUpdatesSnapshot 验证 Add 后新路由生效，旧路由保持
+func TestRouterCoordinatorOnAddRouterUpdatesSnapshot(t *testing.T) {
+	r1 := newTestRouter("r1", "GET", "/foo", "cluster-foo")
+
+	cfg := &model.RouteConfiguration{
+		Routes:  []*model.Router{r1},
+		Dynamic: false,
+	}
+
+	rm := CreateRouterCoordinator(cfg)
+	rm.debounce = 0
+
+	// only /foo
+	mustRouteCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteNotFound(t, rm, "GET", "/bar")
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteByPathAndNameNotFound(t, rm, "GET", "/bar")
+
+	// Add /bar
+	r2 := newTestRouter("r2", "GET", "/bar", "cluster-bar")
+	rm.OnAddRouter(r2)
+
+	// /foo available
+	mustRouteCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/foo", "cluster-foo")
+
+	// /bar available
+	mustRouteCluster(t, rm, "GET", "/bar", "cluster-bar")
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/bar", "cluster-bar")
+}
+
+// TestRouterCoordinatorOnDeleteRouterUpdatesSnapshot 验证 Delete 后路由失效
+func TestRouterCoordinatorOnDeleteRouterUpdatesSnapshot(t *testing.T) {
+	r1 := newTestRouter("r1", "GET", "/foo", "cluster-foo")
+	r2 := newTestRouter("r2", "GET", "/bar", "cluster-bar")
+
+	cfg := &model.RouteConfiguration{
+		Routes:  []*model.Router{r1, r2},
+		Dynamic: false,
+	}
+
+	rm := CreateRouterCoordinator(cfg)
+	rm.debounce = 0
+
+	// /foo、/bar available
+	mustRouteCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteCluster(t, rm, "GET", "/bar", "cluster-bar")
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/bar", "cluster-bar")
+
+	// delete /bar
+	rm.OnDeleteRouter(r2)
+
+	// /foo available
+	mustRouteCluster(t, rm, "GET", "/foo", "cluster-foo")
+	mustRouteByPathAndNameCluster(t, rm, "GET", "/foo", "cluster-foo")
+
+	// /bar fail
+	mustRouteNotFound(t, rm, "GET", "/bar")
+	mustRouteByPathAndNameNotFound(t, rm, "GET", "/bar")
+}
+
+func TestOldVsNew_OnAddRouterWithSameID(t *testing.T) {
+	base := []*model.Router{
+		{
+			ID: "r1",
+			Match: model.RouterMatch{
+				Methods: []string{"POST"},
+				Path:    "/api/v1/item/711",
+			},
+			Route: model.RouteAction{Cluster: "c-x-r1"},
+		},
+	}
+
+	oldc := buildOldCoordinator(base)
+	newc := buildNewCoordinator(base)
+	newc.debounce = 0
+
+	{
+		req, _ := stdHttp.NewRequest("POST", "/api/v1/item/711", nil)
+		ctxOld := http.HttpContext{Request: req}
+		ctxNew := http.HttpContext{Request: req}
+
+		oldRes, oldErr := oldc.Route(&ctxOld)
+		newRes, newErr := newc.Route(&ctxNew)
+
+		if oldErr != nil || newErr != nil {
+			t.Fatalf("initial route error: oldErr=%v newErr=%v", oldErr, newErr)
+		}
+		if oldRes.Cluster != "c-x-r1" || newRes.Cluster != "c-x-r1" {
+			t.Fatalf("initial cluster mismatch: old=%v new=%v", oldRes, newRes)
+		}
+	}
+
+	delta := &model.Router{
+		ID: "r1",
+		Match: model.RouterMatch{
+			Methods: []string{"POST"},
+			Path:    "/api/v1/item/999999",
+		},
+		Route: model.RouteAction{Cluster: "c-x-r1"},
+	}
+
+	oldc.OnAddRouter(delta)
+	newc.OnAddRouter(delta)
+
+	{
+		req, _ := stdHttp.NewRequest("POST", "/api/v1/item/711", nil)
+		ctxOld := http.HttpContext{Request: req}
+		ctxNew := http.HttpContext{Request: req}
+
+		oldRes, oldErr := oldc.Route(&ctxOld)
+		newRes, newErr := newc.Route(&ctxNew)
+
+		assert.Equal(t, oldRes, newRes)
+		assert.Equal(t, oldErr, newErr)
+	}
+
+	{
+		req, _ := stdHttp.NewRequest("POST", "/api/v1/item/999999", nil)
+		ctxOld := http.HttpContext{Request: req}
+		ctxNew := http.HttpContext{Request: req}
+
+		oldRes, oldErr := oldc.Route(&ctxOld)
+		newRes, newErr := newc.Route(&ctxNew)
+
+		assert.Equal(t, oldRes, newRes)
+		assert.Equal(t, oldErr, newErr)
+	}
+}