Repository: envoyproxy/gateway
Pull request #3997 (2024-08-04 13:57:43)

refactor: clean envoygateway validate methods
**What type of PR is this?**
<!--
Your PR title should be descriptive, and generally start with type that contains a subsystem name with `()` if necessary 
and summary followed by a colon. format `chore/docs/api/feat/fix/refactor/style/test: summary`.
Examples:
* "docs: fix grammar error"
* "feat(translator): add new feature"
* "fix: fix xx bug"
* "chore: change ci & build tools etc"
* "api: add xxx fields in ClientTrafficPolicy"
-->

<!--
NOTE: If your PR contains any API changes (changes under `/api`), we recommend you to separate these API changes into
a new PR, and we will review the API part first. It will save you lots of implementation time if the API get accepted.
-->

**What this PR does / why we need it**:

**Which issue(s) this PR fixes**:
<!--
*Automatically closes linked issue when PR is merged.
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->
Prepare for #3159, this part is very likely to get conflicts.


codecov[bot]: ## [Codecov](https://app.codecov.io/gh/envoyproxy/gateway/pull/3997?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) Report
Attention: Patch coverage is `88.76404%` with `10 lines` in your changes missing coverage. Please review.
> Project coverage is 67.67%. Comparing base [(`c2c705b`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/c2c705bd8a027ede944fd134b50e7e61e23b12c5?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) to head [(`7203618`)](https://app.codecov.io/gh/envoyproxy/gateway/commit/72036183c5e72f43be216283007dd5f6d8b47757?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

| [Files](https://app.codecov.io/gh/envoyproxy/gateway/pull/3997?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) | Patch % | Lines |
|---|---|---|
| [api/v1alpha1/validation/envoygateway\_validate.go](https://app.codecov.io/gh/envoyproxy/gateway/pull/3997?src=pr&el=tree&filepath=api%2Fv1alpha1%2Fvalidation%2Fenvoygateway_validate.go&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy#diff-YXBpL3YxYWxwaGExL3ZhbGlkYXRpb24vZW52b3lnYXRld2F5X3ZhbGlkYXRlLmdv) | 88.76% | [8 Missing and 2 partials :warning: ](https://app.codecov.io/gh/envoyproxy/gateway/pull/3997?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy) |

<details><summary>Additional details and impacted files</summary>


```diff
@@            Coverage Diff             @@
##             main    #3997      +/-   ##
==========================================
+ Coverage   67.59%   67.67%   +0.07%     
==========================================
  Files         183      183              
  Lines       22446    22471      +25     
==========================================
+ Hits        15173    15207      +34     
+ Misses       6193     6185       -8     
+ Partials     1080     1079       -1     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/envoyproxy/gateway/pull/3997?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=envoyproxy).

shawnh2: /retest

Patch:
diff --git a/api/v1alpha1/backendtrafficpolicy_types.go b/api/v1alpha1/backendtrafficpolicy_types.go
index a8c39e9df..80e456150 100644
--- a/api/v1alpha1/backendtrafficpolicy_types.go
+++ b/api/v1alpha1/backendtrafficpolicy_types.go
@@ -114,6 +114,11 @@ type BackendTrafficPolicySpec struct {
 	//
 	// +optional
 	DNS *DNS `json:"dns,omitempty"`
+
+	// HTTP2 provides HTTP/2 configuration for backend connections.
+	//
+	// +optional
+	HTTP2 *HTTP2Settings `json:"http2,omitempty"`
 }
 
 // +kubebuilder:object:root=true
diff --git a/api/v1alpha1/clienttrafficpolicy_types.go b/api/v1alpha1/clienttrafficpolicy_types.go
index 397535ebf..347eb9463 100644
--- a/api/v1alpha1/clienttrafficpolicy_types.go
+++ b/api/v1alpha1/clienttrafficpolicy_types.go
@@ -6,7 +6,6 @@
 package v1alpha1
 
 import (
-	"k8s.io/apimachinery/pkg/api/resource"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	gwapiv1a2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
 )
@@ -289,30 +288,6 @@ type HTTP10Settings struct {
 	UseDefaultHost *bool `json:"useDefaultHost,omitempty"`
 }
 
-// HTTP2Settings provides HTTP/2 configuration on the listener.
-type HTTP2Settings struct {
-	// InitialStreamWindowSize sets the initial window size for HTTP/2 streams.
-	// If not set, the default value is 64 KiB(64*1024).
-	//
-	// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="initialStreamWindowSize must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
-	// +optional
-	InitialStreamWindowSize *resource.Quantity `json:"initialStreamWindowSize,omitempty"`
-
-	// InitialConnectionWindowSize sets the initial window size for HTTP/2 connections.
-	// If not set, the default value is 1 MiB.
-	//
-	// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="initialConnectionWindowSize must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
-	// +optional
-	InitialConnectionWindowSize *resource.Quantity `json:"initialConnectionWindowSize,omitempty"`
-
-	// MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection.
-	// If not set, the default value is 100.
-	// +kubebuilder:validation:Minimum=1
-	// +kubebuilder:validation:Maximum=2147483647
-	// +optional
-	MaxConcurrentStreams *uint32 `json:"maxConcurrentStreams,omitempty"`
-}
-
 // HealthCheckSettings provides HealthCheck configuration on the HTTP/HTTPS listener.
 type HealthCheckSettings struct {
 	// Path specifies the HTTP path to match on for health check requests.
diff --git a/api/v1alpha1/shared_types.go b/api/v1alpha1/shared_types.go
index 6ad310859..c151fcd8b 100644
--- a/api/v1alpha1/shared_types.go
+++ b/api/v1alpha1/shared_types.go
@@ -10,6 +10,7 @@ import (
 	autoscalingv2 "k8s.io/api/autoscaling/v2"
 	corev1 "k8s.io/api/core/v1"
 	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
 	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
 )
 
@@ -478,3 +479,41 @@ type BackendRef struct {
 // A CIDR can be an IPv4 address range such as "192.168.1.0/24" or an IPv6 address range such as "2001:0db8:11a3:09d7::/64".
 // +kubebuilder:validation:Pattern=`((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]+))|((([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\/([0-9]+))`
 type CIDR string
+
+type InvalidMessageAction string
+
+const (
+	InvalidMessageActionTerminateConnection InvalidMessageAction = "TerminateConnection"
+	InvalidMessageActionTerminateStream     InvalidMessageAction = "TerminateStream"
+)
+
+// HTTP2Settings provides HTTP/2 configuration for listeners and backends.
+type HTTP2Settings struct {
+	// InitialStreamWindowSize sets the initial window size for HTTP/2 streams.
+	// If not set, the default value is 64 KiB(64*1024).
+	//
+	// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="initialStreamWindowSize must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
+	// +optional
+	InitialStreamWindowSize *resource.Quantity `json:"initialStreamWindowSize,omitempty"`
+
+	// InitialConnectionWindowSize sets the initial window size for HTTP/2 connections.
+	// If not set, the default value is 1 MiB.
+	//
+	// +kubebuilder:validation:XValidation:rule="type(self) == string ? self.matches(r\"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\") : type(self) == int",message="initialConnectionWindowSize must be of the format \"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$\""
+	// +optional
+	InitialConnectionWindowSize *resource.Quantity `json:"initialConnectionWindowSize,omitempty"`
+
+	// MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection.
+	// If not set, the default value is 100.
+	// +kubebuilder:validation:Minimum=1
+	// +kubebuilder:validation:Maximum=2147483647
+	// +optional
+	MaxConcurrentStreams *uint32 `json:"maxConcurrentStreams,omitempty"`
+
+	// OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error
+	// It's recommended for L2 Envoy deployments to set this value to TerminateStream.
+	// https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two
+	// Default: TerminateConnection
+	// +optional
+	OnInvalidMessage *InvalidMessageAction `json:"onInvalidMessage,omitempty"`
+}
diff --git a/api/v1alpha1/validation/envoygateway_validate.go b/api/v1alpha1/validation/envoygateway_validate.go
index 91d61bcc8..8d7c97dc9 100644
--- a/api/v1alpha1/validation/envoygateway_validate.go
+++ b/api/v1alpha1/validation/envoygateway_validate.go
@@ -6,115 +6,172 @@
 package validation
 
 import (
-	"errors"
 	"fmt"
 	"net/url"
 
-	gwapiv1 "sigs.k8s.io/gateway-api/apis/v1"
-
 	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
 )
 
 // ValidateEnvoyGateway validates the provided EnvoyGateway.
 func ValidateEnvoyGateway(eg *egv1a1.EnvoyGateway) error {
-	switch {
-	case eg == nil:
-		return errors.New("envoy gateway config is unspecified")
-	case eg.Gateway == nil:
-		return errors.New("gateway is unspecified")
-	case len(eg.Gateway.ControllerName) == 0:
-		return errors.New("gateway controllerName is unspecified")
-	case eg.Provider == nil:
-		return errors.New("provider is unspecified")
-	case eg.Provider.Type != egv1a1.ProviderTypeKubernetes:
-		return fmt.Errorf("unsupported provider %v", eg.Provider.Type)
-	case eg.Provider.Kubernetes != nil && eg.Provider.Kubernetes.Watch != nil:
-		watch := eg.Provider.Kubernetes.Watch
-		switch watch.Type {
-		case egv1a1.KubernetesWatchModeTypeNamespaces:
-			if len(watch.Namespaces) == 0 {
-				return errors.New("namespaces should be specified when envoy gateway watch mode is 'Namespaces'")
-			}
-		case egv1a1.KubernetesWatchModeTypeNamespaceSelector:
-			if watch.NamespaceSelector == nil {
-				return errors.New("namespaceSelector should be specified when envoy gateway watch mode is 'NamespaceSelector'")
-			}
-		default:
-			return errors.New("envoy gateway watch mode invalid, should be 'Namespaces' or 'NamespaceSelector'")
-		}
-	case eg.Logging != nil && len(eg.Logging.Level) != 0:
-		level := eg.Logging.Level
-		for component, logLevel := range level {
-			switch component {
-			case egv1a1.LogComponentGatewayDefault,
-				egv1a1.LogComponentProviderRunner,
-				egv1a1.LogComponentGatewayAPIRunner,
-				egv1a1.LogComponentXdsTranslatorRunner,
-				egv1a1.LogComponentXdsServerRunner,
-				egv1a1.LogComponentInfrastructureRunner,
-				egv1a1.LogComponentGlobalRateLimitRunner:
-				switch logLevel {
-				case egv1a1.LogLevelDebug, egv1a1.LogLevelError, egv1a1.LogLevelWarn, egv1a1.LogLevelInfo:
-				default:
-					return errors.New("envoy gateway logging level invalid. valid options: info/debug/warn/error")
-				}
-			default:
-				return errors.New("envoy gateway logging components invalid. valid options: system/provider/gateway-api/xds-translator/xds-server/infrastructure")
-			}
-		}
-	case eg.RateLimit != nil:
-		if eg.RateLimit.Backend.Type != egv1a1.RedisBackendType {
-			return fmt.Errorf("unsupported ratelimit backend %v", eg.RateLimit.Backend.Type)
-		}
-		if eg.RateLimit.Backend.Redis == nil || eg.RateLimit.Backend.Redis.URL == "" {
-			return fmt.Errorf("empty ratelimit redis settings")
+	if eg == nil {
+		return fmt.Errorf("envoy gateway config is unspecified")
+	}
+
+	if eg.Gateway == nil {
+		return fmt.Errorf("gateway is unspecified")
+	}
+
+	if len(eg.Gateway.ControllerName) == 0 {
+		return fmt.Errorf("gateway controllerName is unspecified")
+	}
+
+	if eg.Provider == nil {
+		return fmt.Errorf("provider is unspecified")
+	}
+
+	switch eg.Provider.Type {
+	case egv1a1.ProviderTypeKubernetes:
+		if err := validateEnvoyGatewayKubernetesProvider(eg.Provider.Kubernetes); err != nil {
+			return err
 		}
-		if _, err := url.Parse(eg.RateLimit.Backend.Redis.URL); err != nil {
-			return fmt.Errorf("unknown ratelimit redis url format: %w", err)
+	default:
+		return fmt.Errorf("unsupported provider type")
+	}
+
+	if err := validateEnvoyGatewayLogging(eg.Logging); err != nil {
+		return err
+	}
+
+	if err := validateEnvoyGatewayRateLimit(eg.RateLimit); err != nil {
+		return err
+	}
+
+	if err := validateEnvoyGatewayExtensionManager(eg.ExtensionManager); err != nil {
+		return err
+	}
+
+	if err := validateEnvoyGatewayTelemetry(eg.Telemetry); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+func validateEnvoyGatewayKubernetesProvider(provider *egv1a1.EnvoyGatewayKubernetesProvider) error {
+	if provider == nil || provider.Watch == nil {
+		return nil
+	}
+
+	watch := provider.Watch
+	switch watch.Type {
+	case egv1a1.KubernetesWatchModeTypeNamespaces:
+		if len(watch.Namespaces) == 0 {
+			return fmt.Errorf("namespaces should be specified when envoy gateway watch mode is 'Namespaces'")
 		}
-	case eg.ExtensionManager != nil:
-		if eg.ExtensionManager.Hooks == nil || eg.ExtensionManager.Hooks.XDSTranslator == nil {
-			return fmt.Errorf("registered extension has no hooks specified")
+	case egv1a1.KubernetesWatchModeTypeNamespaceSelector:
+		if watch.NamespaceSelector == nil {
+			return fmt.Errorf("namespaceSelector should be specified when envoy gateway watch mode is 'NamespaceSelector'")
 		}
+	default:
+		return fmt.Errorf("envoy gateway watch mode invalid, should be 'Namespaces' or 'NamespaceSelector'")
+	}
+	return nil
+}
 
-		if len(eg.ExtensionManager.Hooks.XDSTranslator.Pre) == 0 && len(eg.ExtensionManager.Hooks.XDSTranslator.Post) == 0 {
-			return fmt.Errorf("registered extension has no hooks specified")
-		}
+func validateEnvoyGatewayLogging(logging *egv1a1.EnvoyGatewayLogging) error {
+	if logging == nil || len(logging.Level) == 0 {
+		return nil
+	}
 
-		if eg.ExtensionManager.Service == nil {
-			return fmt.Errorf("extension service config is empty")
+	for component, logLevel := range logging.Level {
+		switch component {
+		case egv1a1.LogComponentGatewayDefault,
+			egv1a1.LogComponentProviderRunner,
+			egv1a1.LogComponentGatewayAPIRunner,
+			egv1a1.LogComponentXdsTranslatorRunner,
+			egv1a1.LogComponentXdsServerRunner,
+			egv1a1.LogComponentInfrastructureRunner,
+			egv1a1.LogComponentGlobalRateLimitRunner:
+			switch logLevel {
+			case egv1a1.LogLevelDebug, egv1a1.LogLevelError, egv1a1.LogLevelWarn, egv1a1.LogLevelInfo:
+			default:
+				return fmt.Errorf("envoy gateway logging level invalid. valid options: info/debug/warn/error")
+			}
+		default:
+			return fmt.Errorf("envoy gateway logging components invalid. valid options: system/provider/gateway-api/xds-translator/xds-server/infrastructure")
 		}
+	}
+	return nil
+}
 
-		switch {
-		case eg.ExtensionManager.Service.Host == "" && eg.ExtensionManager.Service.FQDN == nil && eg.ExtensionManager.Service.Unix == nil && eg.ExtensionManager.Service.IP == nil:
-			return fmt.Errorf("extension service must contain a configured target")
+func validateEnvoyGatewayRateLimit(rateLimit *egv1a1.RateLimit) error {
+	if rateLimit == nil {
+		return nil
+	}
+	if rateLimit.Backend.Type != egv1a1.RedisBackendType {
+		return fmt.Errorf("unsupported ratelimit backend %v", rateLimit.Backend.Type)
+	}
+	if rateLimit.Backend.Redis == nil || rateLimit.Backend.Redis.URL == "" {
+		return fmt.Errorf("empty ratelimit redis settings")
+	}
+	if _, err := url.Parse(rateLimit.Backend.Redis.URL); err != nil {
+		return fmt.Errorf("unknown ratelimit redis url format: %w", err)
+	}
+	return nil
+}
 
-		case eg.ExtensionManager.Service.FQDN != nil && (eg.ExtensionManager.Service.IP != nil || eg.ExtensionManager.Service.Unix != nil || eg.ExtensionManager.Service.Host != ""),
-			eg.ExtensionManager.Service.IP != nil && (eg.ExtensionManager.Service.FQDN != nil || eg.ExtensionManager.Service.Unix != nil || eg.ExtensionManager.Service.Host != ""),
-			eg.ExtensionManager.Service.Unix != nil && (eg.ExtensionManager.Service.IP != nil || eg.ExtensionManager.Service.FQDN != nil || eg.ExtensionManager.Service.Host != ""):
+func validateEnvoyGatewayExtensionManager(extensionManager *egv1a1.ExtensionManager) error {
+	if extensionManager == nil {
+		return nil
+	}
 
-			return fmt.Errorf("only one backend target can be configured for the extension manager")
+	if extensionManager.Hooks == nil || extensionManager.Hooks.XDSTranslator == nil {
+		return fmt.Errorf("registered extension has no hooks specified")
+	}
 
-		}
+	if len(extensionManager.Hooks.XDSTranslator.Pre) == 0 && len(extensionManager.Hooks.XDSTranslator.Post) == 0 {
+		return fmt.Errorf("registered extension has no hooks specified")
+	}
 
-		if eg.ExtensionManager.Service.TLS != nil {
-			certificateRefKind := eg.ExtensionManager.Service.TLS.CertificateRef.Kind
+	if extensionManager.Service == nil {
+		return fmt.Errorf("extension service config is empty")
+	}
 
-			if certificateRefKind == nil {
-				return fmt.Errorf("certificateRef empty in extension service server TLS settings")
-			}
+	switch {
+	case extensionManager.Service.Host == "" && extensionManager.Service.FQDN == nil && extensionManager.Service.Unix == nil && extensionManager.Service.IP == nil:
+		return fmt.Errorf("extension service must contain a configured target")
 
-			if *certificateRefKind != gwapiv1.Kind("Secret") {
-				return fmt.Errorf("unsupported extension server TLS certificateRef %v", certificateRefKind)
-			}
+	case extensionManager.Service.FQDN != nil && (extensionManager.Service.IP != nil || extensionManager.Service.Unix != nil || extensionManager.Service.Host != ""),
+		extensionManager.Service.IP != nil && (extensionManager.Service.FQDN != nil || extensionManager.Service.Unix != nil || extensionManager.Service.Host != ""),
+		extensionManager.Service.Unix != nil && (extensionManager.Service.IP != nil || extensionManager.Service.FQDN != nil || extensionManager.Service.Host != ""):
+		return fmt.Errorf("only one backend target can be configured for the extension manager")
+	}
+
+	if extensionManager.Service.TLS != nil {
+		certificateRefKind := extensionManager.Service.TLS.CertificateRef.Kind
+
+		if certificateRefKind == nil {
+			return fmt.Errorf("certificateRef empty in extension service server TLS settings")
 		}
-	case eg.Telemetry != nil:
-		if eg.Telemetry.Metrics != nil {
-			for _, sink := range eg.Telemetry.Metrics.Sinks {
-				if sink.Type == egv1a1.MetricSinkTypeOpenTelemetry {
-					if sink.OpenTelemetry == nil {
-						return fmt.Errorf("OpenTelemetry is required when sink Type is OpenTelemetry")
-					}
+
+		if *certificateRefKind != "Secret" {
+			return fmt.Errorf("unsupported extension server TLS certificateRef %v", certificateRefKind)
+		}
+	}
+	return nil
+}
+
+func validateEnvoyGatewayTelemetry(telemetry *egv1a1.EnvoyGatewayTelemetry) error {
+	if telemetry == nil {
+		return nil
+	}
+
+	if telemetry.Metrics != nil {
+		for _, sink := range telemetry.Metrics.Sinks {
+			if sink.Type == egv1a1.MetricSinkTypeOpenTelemetry {
+				if sink.OpenTelemetry == nil {
+					return fmt.Errorf("OpenTelemetry is required when sink Type is OpenTelemetry")
 				}
 			}
 		}
diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go
index e6c4da29a..958025f1d 100644
--- a/api/v1alpha1/zz_generated.deepcopy.go
+++ b/api/v1alpha1/zz_generated.deepcopy.go
@@ -574,6 +574,11 @@ func (in *BackendTrafficPolicySpec) DeepCopyInto(out *BackendTrafficPolicySpec)
 		*out = new(DNS)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.HTTP2 != nil {
+		in, out := &in.HTTP2, &out.HTTP2
+		*out = new(HTTP2Settings)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackendTrafficPolicySpec.
@@ -2448,6 +2453,11 @@ func (in *HTTP2Settings) DeepCopyInto(out *HTTP2Settings) {
 		*out = new(uint32)
 		**out = **in
 	}
+	if in.OnInvalidMessage != nil {
+		in, out := &in.OnInvalidMessage, &out.OnInvalidMessage
+		*out = new(InvalidMessageAction)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP2Settings.
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
index 2ddf8c841..7678f5eaf 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_backendtrafficpolicies.yaml
@@ -436,6 +436,51 @@ spec:
                         type: boolean
                     type: object
                 type: object
+              http2:
+                description: HTTP2 provides HTTP/2 configuration for backend connections.
+                properties:
+                  initialConnectionWindowSize:
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    description: |-
+                      InitialConnectionWindowSize sets the initial window size for HTTP/2 connections.
+                      If not set, the default value is 1 MiB.
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                    x-kubernetes-int-or-string: true
+                    x-kubernetes-validations:
+                    - message: initialConnectionWindowSize must be of the format "^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$"
+                      rule: 'type(self) == string ? self.matches(r"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$")
+                        : type(self) == int'
+                  initialStreamWindowSize:
+                    anyOf:
+                    - type: integer
+                    - type: string
+                    description: |-
+                      InitialStreamWindowSize sets the initial window size for HTTP/2 streams.
+                      If not set, the default value is 64 KiB(64*1024).
+                    pattern: ^(\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))(([KMGTPE]i)|[numkMGTPE]|([eE](\+|-)?(([0-9]+(\.[0-9]*)?)|(\.[0-9]+))))?$
+                    x-kubernetes-int-or-string: true
+                    x-kubernetes-validations:
+                    - message: initialStreamWindowSize must be of the format "^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$"
+                      rule: 'type(self) == string ? self.matches(r"^[1-9]+[0-9]*([EPTGMK]i|[EPTGMk])?$")
+                        : type(self) == int'
+                  maxConcurrentStreams:
+                    description: |-
+                      MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection.
+                      If not set, the default value is 100.
+                    format: int32
+                    maximum: 2147483647
+                    minimum: 1
+                    type: integer
+                  onInvalidMessage:
+                    description: |-
+                      OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error
+                      It's recommended for L2 Envoy deployments to set this value to TerminateStream.
+                      https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two
+                      Default: TerminateConnection
+                    type: string
+                type: object
               loadBalancer:
                 description: |-
                   LoadBalancer policy to apply when routing traffic from the gateway to
diff --git a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
index 48cfb9f3a..36a05061f 100644
--- a/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
+++ b/charts/gateway-helm/crds/generated/gateway.envoyproxy.io_clienttrafficpolicies.yaml
@@ -300,6 +300,13 @@ spec:
                     maximum: 2147483647
                     minimum: 1
                     type: integer
+                  onInvalidMessage:
+                    description: |-
+                      OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error
+                      It's recommended for L2 Envoy deployments to set this value to TerminateStream.
+                      https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two
+                      Default: TerminateConnection
+                    type: string
                 type: object
               http3:
                 description: HTTP3 provides HTTP/3 configuration on the listener.
diff --git a/internal/gatewayapi/backendtrafficpolicy.go b/internal/gatewayapi/backendtrafficpolicy.go
index 65d4404df..120918a8f 100644
--- a/internal/gatewayapi/backendtrafficpolicy.go
+++ b/internal/gatewayapi/backendtrafficpolicy.go
@@ -296,6 +296,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 		rt        *ir.Retry
 		bc        *ir.BackendConnection
 		ds        *ir.DNS
+		h2        *ir.HTTP2Settings
 		err, errs error
 	)
 
@@ -350,6 +351,13 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 		}
 	}
 
+	if policy.Spec.HTTP2 != nil {
+		if h2, err = buildIRHTTP2Settings(policy.Spec.HTTP2); err != nil {
+			err = perr.WithMessage(err, "HTTP2")
+			errs = errors.Join(errs, err)
+		}
+	}
+
 	if policy.Spec.DNS != nil {
 		ds = t.translateDNS(policy)
 	}
@@ -405,6 +413,7 @@ func (t *Translator) translateBackendTrafficPolicyForRoute(policy *egv1a1.Backen
 						TCPKeepalive:      ka,
 						Retry:             rt,
 						BackendConnection: bc,
+						HTTP2:             h2,
 					}
 
 					r.DNS = ds
@@ -441,6 +450,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 		ka        *ir.TCPKeepalive
 		rt        *ir.Retry
 		ds        *ir.DNS
+		h2        *ir.HTTP2Settings
 		err, errs error
 	)
 
@@ -487,6 +497,12 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 			errs = errors.Join(errs, err)
 		}
 	}
+	if policy.Spec.HTTP2 != nil {
+		if h2, err = buildIRHTTP2Settings(policy.Spec.HTTP2); err != nil {
+			err = perr.WithMessage(err, "HTTP2")
+			errs = errors.Join(errs, err)
+		}
+	}
 
 	if policy.Spec.DNS != nil {
 		ds = t.translateDNS(policy)
@@ -587,6 +603,7 @@ func (t *Translator) translateBackendTrafficPolicyForGateway(policy *egv1a1.Back
 				FaultInjection: fi,
 				TCPKeepalive:   ka,
 				Retry:          rt,
+				HTTP2:          h2,
 			}
 
 			if r.DNS == nil {
diff --git a/internal/gatewayapi/clienttrafficpolicy.go b/internal/gatewayapi/clienttrafficpolicy.go
index 8462a0545..1d7e8e89a 100644
--- a/internal/gatewayapi/clienttrafficpolicy.go
+++ b/internal/gatewayapi/clienttrafficpolicy.go
@@ -28,11 +28,7 @@ import (
 
 const (
 	// Use an invalid string to represent all sections (listeners) within a Gateway
-	AllSections                         = "/"
-	MinHTTP2InitialStreamWindowSize     = 65535      // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-http2protocoloptions-initial-stream-window-size
-	MaxHTTP2InitialStreamWindowSize     = 2147483647 // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-http2protocoloptions-initial-stream-window-size
-	MinHTTP2InitialConnectionWindowSize = MinHTTP2InitialStreamWindowSize
-	MaxHTTP2InitialConnectionWindowSize = MaxHTTP2InitialStreamWindowSize
+	AllSections = "/"
 )
 
 func hasSectionName(target *gwapiv1a2.LocalPolicyTargetReferenceWithSectionName) bool {
diff --git a/internal/gatewayapi/http.go b/internal/gatewayapi/http.go
new file mode 100644
index 000000000..95b0cd631
--- /dev/null
+++ b/internal/gatewayapi/http.go
@@ -0,0 +1,73 @@
+// Copyright Envoy Gateway Authors
+// SPDX-License-Identifier: Apache-2.0
+// The full text of the Apache license is available in the LICENSE file at
+// the root of the repo.
+
+package gatewayapi
+
+import (
+	"errors"
+	"fmt"
+
+	"k8s.io/utils/ptr"
+
+	egv1a1 "github.com/envoyproxy/gateway/api/v1alpha1"
+	"github.com/envoyproxy/gateway/internal/ir"
+)
+
+const (
+	MinHTTP2InitialStreamWindowSize     = 65535      // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-http2protocoloptions-initial-stream-window-size
+	MaxHTTP2InitialStreamWindowSize     = 2147483647 // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-field-config-core-v3-http2protocoloptions-initial-stream-window-size
+	MinHTTP2InitialConnectionWindowSize = MinHTTP2InitialStreamWindowSize
+	MaxHTTP2InitialConnectionWindowSize = MaxHTTP2InitialStreamWindowSize
+)
+
+func buildIRHTTP2Settings(http2Settings *egv1a1.HTTP2Settings) (*ir.HTTP2Settings, error) {
+	var (
+		http2 = &ir.HTTP2Settings{}
+		errs  error
+	)
+
+	if http2Settings.InitialStreamWindowSize != nil {
+		initialStreamWindowSize, ok := http2Settings.InitialStreamWindowSize.AsInt64()
+		switch {
+		case !ok:
+			errs = errors.Join(errs, fmt.Errorf("invalid InitialStreamWindowSize value %s", http2Settings.InitialStreamWindowSize.String()))
+		case initialStreamWindowSize < MinHTTP2InitialStreamWindowSize || initialStreamWindowSize > MaxHTTP2InitialStreamWindowSize:
+			errs = errors.Join(errs, fmt.Errorf("InitialStreamWindowSize value %s is out of range, must be between %d and %d",
+				http2Settings.InitialStreamWindowSize.String(),
+				MinHTTP2InitialStreamWindowSize,
+				MaxHTTP2InitialStreamWindowSize))
+		default:
+			http2.InitialStreamWindowSize = ptr.To(uint32(initialStreamWindowSize))
+		}
+	}
+
+	if http2Settings.InitialConnectionWindowSize != nil {
+		initialConnectionWindowSize, ok := http2Settings.InitialConnectionWindowSize.AsInt64()
+		switch {
+		case !ok:
+			errs = errors.Join(errs, fmt.Errorf("invalid InitialConnectionWindowSize value %s", http2Settings.InitialConnectionWindowSize.String()))
+		case initialConnectionWindowSize < MinHTTP2InitialConnectionWindowSize || initialConnectionWindowSize > MaxHTTP2InitialConnectionWindowSize:
+			errs = errors.Join(errs, fmt.Errorf("InitialConnectionWindowSize value %s is out of range, must be between %d and %d",
+				http2Settings.InitialConnectionWindowSize.String(),
+				MinHTTP2InitialConnectionWindowSize,
+				MaxHTTP2InitialConnectionWindowSize))
+		default:
+			http2.InitialConnectionWindowSize = ptr.To(uint32(initialConnectionWindowSize))
+		}
+	}
+
+	http2.MaxConcurrentStreams = http2Settings.MaxConcurrentStreams
+
+	if http2Settings.OnInvalidMessage != nil {
+		switch *http2Settings.OnInvalidMessage {
+		case egv1a1.InvalidMessageActionTerminateStream:
+			http2.ResetStreamOnError = ptr.To(true)
+		case egv1a1.InvalidMessageActionTerminateConnection:
+			http2.ResetStreamOnError = ptr.To(false)
+		}
+	}
+
+	return http2, errs
+}
diff --git a/internal/ir/xds.go b/internal/ir/xds.go
index f4f059a2a..7cc5ed8f3 100644
--- a/internal/ir/xds.go
+++ b/internal/ir/xds.go
@@ -456,6 +456,8 @@ type HTTP2Settings struct {
 	InitialConnectionWindowSize *uint32 `json:"initialStreamWindowSize,omitempty" yaml:"initialStreamWindowSize,omitempty"`
 	// MaxConcurrentStreams is the maximum number of concurrent streams that can be opened on a connection.
 	MaxConcurrentStreams *uint32 `json:"maxConcurrentStreams,omitempty" yaml:"maxConcurrentStreams,omitempty"`
+	// ResetStreamOnError determines if a stream or connection is reset on messaging error.
+	ResetStreamOnError *bool `json:"resetStreamOnError,omitempty" yaml:"resetStreamOnError,omitempty"`
 }
 
 // HealthCheckSettings provides HealthCheck configuration on the HTTP/HTTPS listener.
@@ -600,6 +602,9 @@ type TrafficFeatures struct {
 	Retry *Retry `json:"retry,omitempty" yaml:"retry,omitempty"`
 	// settings of upstream connection
 	BackendConnection *BackendConnection `json:"backendConnection,omitempty" yaml:"backendConnection,omitempty"`
+	// HTTP2 provides HTTP/2 configuration for clusters
+	// +optional
+	HTTP2 *HTTP2Settings `json:"http2,omitempty" yaml:"http2,omitempty"`
 }
 
 func (b *TrafficFeatures) Validate() error {
diff --git a/internal/ir/zz_generated.deepcopy.go b/internal/ir/zz_generated.deepcopy.go
index e0a7b4557..dabb7af5a 100644
--- a/internal/ir/zz_generated.deepcopy.go
+++ b/internal/ir/zz_generated.deepcopy.go
@@ -1030,6 +1030,11 @@ func (in *HTTP2Settings) DeepCopyInto(out *HTTP2Settings) {
 		*out = new(uint32)
 		**out = **in
 	}
+	if in.ResetStreamOnError != nil {
+		in, out := &in.ResetStreamOnError, &out.ResetStreamOnError
+		*out = new(bool)
+		**out = **in
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HTTP2Settings.
@@ -2893,6 +2898,11 @@ func (in *TrafficFeatures) DeepCopyInto(out *TrafficFeatures) {
 		*out = new(BackendConnection)
 		(*in).DeepCopyInto(*out)
 	}
+	if in.HTTP2 != nil {
+		in, out := &in.HTTP2, &out.HTTP2
+		*out = new(HTTP2Settings)
+		(*in).DeepCopyInto(*out)
+	}
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TrafficFeatures.
diff --git a/internal/xds/translator/cluster.go b/internal/xds/translator/cluster.go
index 9a7819a59..697736dbc 100644
--- a/internal/xds/translator/cluster.go
+++ b/internal/xds/translator/cluster.go
@@ -48,6 +48,7 @@ type xdsClusterArgs struct {
 	circuitBreaker    *ir.CircuitBreaker
 	healthCheck       *ir.HealthCheck
 	http1Settings     *ir.HTTP1Settings
+	http2Settings     *ir.HTTP2Settings
 	timeout           *ir.Timeout
 	tcpkeepalive      *ir.TCPKeepalive
 	metrics           *ir.Metrics
@@ -515,13 +516,15 @@ func buildTypedExtensionProtocolOptions(args *xdsClusterArgs) map[string]*anypb.
 		protocolOptions.UpstreamProtocolOptions = &httpv3.HttpProtocolOptions_UseDownstreamProtocolConfig{
 			UseDownstreamProtocolConfig: &httpv3.HttpProtocolOptions_UseDownstreamHttpConfig{
 				HttpProtocolOptions:  http1opts,
-				Http2ProtocolOptions: &corev3.Http2ProtocolOptions{},
+				Http2ProtocolOptions: buildHTTP2Settings(args.http2Settings),
 			},
 		}
 	case requiresHTTP2Options:
 		protocolOptions.UpstreamProtocolOptions = &httpv3.HttpProtocolOptions_ExplicitHttpConfig_{
 			ExplicitHttpConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig{
-				ProtocolConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_Http2ProtocolOptions{},
+				ProtocolConfig: &httpv3.HttpProtocolOptions_ExplicitHttpConfig_Http2ProtocolOptions{
+					Http2ProtocolOptions: buildHTTP2Settings(args.http2Settings),
+				},
 			},
 		}
 	case requiresHTTP1Options:
@@ -664,6 +667,7 @@ func buildBackandConnectionBufferLimitBytes(bc *ir.BackendConnection) *wrappers.
 type ExtraArgs struct {
 	metrics       *ir.Metrics
 	http1Settings *ir.HTTP1Settings
+	http2Settings *ir.HTTP2Settings
 }
 
 type clusterArgs interface {
@@ -721,6 +725,7 @@ func (httpRoute *HTTPRouteTranslator) asClusterArgs(extra *ExtraArgs) *xdsCluste
 		endpointType:      buildEndpointType(httpRoute.Destination.Settings),
 		metrics:           extra.metrics,
 		http1Settings:     extra.http1Settings,
+		http2Settings:     extra.http2Settings,
 		useClientProtocol: ptr.Deref(httpRoute.UseClientProtocol, false),
 	}
 
@@ -742,3 +747,33 @@ func (httpRoute *HTTPRouteTranslator) asClusterArgs(extra *ExtraArgs) *xdsCluste
 
 	return clusterArgs
 }
+
+func buildHTTP2Settings(opts *ir.HTTP2Settings) *corev3.Http2ProtocolOptions {
+	if opts == nil {
+		opts = &ir.HTTP2Settings{}
+	}
+
+	// defaults based on https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/edge
+	out := &corev3.Http2ProtocolOptions{
+		InitialStreamWindowSize: &wrapperspb.UInt32Value{
+			Value: ptr.Deref(opts.InitialStreamWindowSize, http2InitialStreamWindowSize),
+		},
+		InitialConnectionWindowSize: &wrapperspb.UInt32Value{
+			Value: ptr.Deref(opts.InitialConnectionWindowSize, http2InitialConnectionWindowSize),
+		},
+	}
+
+	if opts.MaxConcurrentStreams != nil {
+		out.MaxConcurrentStreams = &wrapperspb.UInt32Value{
+			Value: *opts.MaxConcurrentStreams,
+		}
+	}
+
+	if opts.ResetStreamOnError != nil {
+		out.OverrideStreamErrorOnInvalidHttpMessage = &wrapperspb.BoolValue{
+			Value: *opts.ResetStreamOnError,
+		}
+	}
+
+	return out
+}
diff --git a/internal/xds/translator/listener.go b/internal/xds/translator/listener.go
index c3dc05ebc..0812010ad 100644
--- a/internal/xds/translator/listener.go
+++ b/internal/xds/translator/listener.go
@@ -86,7 +86,7 @@ func http2ProtocolOptions(opts *ir.HTTP2Settings) *corev3.Http2ProtocolOptions {
 		opts = &ir.HTTP2Settings{}
 	}
 
-	return &corev3.Http2ProtocolOptions{
+	out := &corev3.Http2ProtocolOptions{
 		MaxConcurrentStreams: &wrapperspb.UInt32Value{
 			Value: ptr.Deref(opts.MaxConcurrentStreams, http2MaxConcurrentStreamsLimit),
 		},
@@ -97,6 +97,14 @@ func http2ProtocolOptions(opts *ir.HTTP2Settings) *corev3.Http2ProtocolOptions {
 			Value: ptr.Deref(opts.InitialConnectionWindowSize, http2InitialConnectionWindowSize),
 		},
 	}
+
+	if opts.ResetStreamOnError != nil {
+		out.OverrideStreamErrorOnInvalidHttpMessage = &wrapperspb.BoolValue{
+			Value: *opts.ResetStreamOnError,
+		}
+	}
+
+	return out
 }
 
 func xffNumTrustedHops(clientIPDetection *ir.ClientIPDetectionSettings) uint32 {
diff --git a/internal/xds/translator/route.go b/internal/xds/translator/route.go
index 2b9c75a2e..8a56e9e42 100644
--- a/internal/xds/translator/route.go
+++ b/internal/xds/translator/route.go
@@ -401,7 +401,10 @@ func buildXdsURLRewriteAction(destName string, urlRewrite *ir.URLRewrite, pathMa
 			if useRegexRewriteForPrefixMatchReplace(pathMatch, *urlRewrite.Path.PrefixMatchReplace) {
 				routeAction.RegexRewrite = prefix2RegexRewrite(*pathMatch.Prefix)
 			} else {
-				routeAction.PrefixRewrite = *urlRewrite.Path.PrefixMatchReplace
+				// remove trailing / to fix #3989
+				// when the pathMath.Prefix has suffix / but EG has removed it,
+				// and the urlRewrite.Path.PrefixMatchReplace suffix with / the upstream will get unwanted /
+				routeAction.PrefixRewrite = strings.TrimSuffix(*urlRewrite.Path.PrefixMatchReplace, "/")
 			}
 		}
 	}
diff --git a/internal/xds/translator/translator.go b/internal/xds/translator/translator.go
index 861e36ca8..9fa5e7009 100644
--- a/internal/xds/translator/translator.go
+++ b/internal/xds/translator/translator.go
@@ -450,13 +450,19 @@ func (t *Translator) addRouteToRouteConfig(
 		vHost.Routes = append(vHost.Routes, xdsRoute)
 
 		if httpRoute.Destination != nil {
+			ea := &ExtraArgs{
+				metrics:       metrics,
+				http1Settings: httpListener.HTTP1,
+			}
+
+			if httpRoute.Traffic != nil && httpRoute.Traffic.HTTP2 != nil {
+				ea.http2Settings = httpRoute.Traffic.HTTP2
+			}
+
 			if err = processXdsCluster(
 				tCtx,
 				&HTTPRouteTranslator{httpRoute},
-				&ExtraArgs{
-					metrics:       metrics,
-					http1Settings: httpListener.HTTP1,
-				},
+				ea,
 			); err != nil {
 				errs = errors.Join(errs, err)
 			}


Test patch:
diff --git a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
index d1872e3f5..4f2d28921 100644
--- a/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/default-resources.all.yaml
@@ -804,7 +804,9 @@ xds:
             envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
               '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
               explicitHttpConfig:
-                http2ProtocolOptions: {}
+                http2ProtocolOptions:
+                  initialConnectionWindowSize: 1048576
+                  initialStreamWindowSize: 65536
       - cluster:
           '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
           circuitBreakers:
diff --git a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json
index 56952759b..aa5af9908 100644
--- a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json
+++ b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.json
@@ -504,7 +504,10 @@
                   "envoy.extensions.upstreams.http.v3.HttpProtocolOptions": {
                     "@type": "type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions",
                     "explicitHttpConfig": {
-                      "http2ProtocolOptions": {}
+                      "http2ProtocolOptions": {
+                        "initialConnectionWindowSize": 1048576,
+                        "initialStreamWindowSize": 65536
+                      }
                     }
                   }
                 }
diff --git a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml
index 6f0229789..1c53aef7e 100644
--- a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.all.yaml
@@ -285,7 +285,9 @@ xds:
             envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
               '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
               explicitHttpConfig:
-                http2ProtocolOptions: {}
+                http2ProtocolOptions:
+                  initialConnectionWindowSize: 1048576
+                  initialStreamWindowSize: 65536
       - cluster:
           '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
           circuitBreakers:
diff --git a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.cluster.yaml b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.cluster.yaml
index 3d9bce8ad..c9f782804 100644
--- a/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.cluster.yaml
+++ b/internal/cmd/egctl/testdata/translate/out/from-gateway-api-to-xds.cluster.yaml
@@ -44,7 +44,9 @@ xds:
           envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
             '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
             explicitHttpConfig:
-              http2ProtocolOptions: {}
+              http2ProtocolOptions:
+                initialConnectionWindowSize: 1048576
+                initialStreamWindowSize: 65536
     - cluster:
         '@type': type.googleapis.com/envoy.config.cluster.v3.Cluster
         circuitBreakers:
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.in.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.in.yaml
new file mode 100644
index 000000000..f89621941
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.in.yaml
@@ -0,0 +1,95 @@
+gateways:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: envoy-gateway
+      name: gateway-1
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: Gateway
+    metadata:
+      namespace: envoy-gateway
+      name: gateway-2
+    spec:
+      gatewayClassName: envoy-gateway-class
+      listeners:
+        - name: http
+          protocol: HTTP
+          port: 80
+          allowedRoutes:
+            namespaces:
+              from: All
+grpcRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1alpha2
+    kind: GRPCRoute
+    metadata:
+      namespace: default
+      name: grpcroute-1
+    spec:
+      parentRefs:
+        - namespace: envoy-gateway
+          name: gateway-1
+          sectionName: http
+      rules:
+        - backendRefs:
+            - name: service-1
+              port: 8080
+httpRoutes:
+  - apiVersion: gateway.networking.k8s.io/v1
+    kind: HTTPRoute
+    metadata:
+      namespace: default
+      name: httproute-1
+    spec:
+      hostnames:
+        - gateway.envoyproxy.io
+      parentRefs:
+        - namespace: envoy-gateway
+          name: gateway-2
+          sectionName: http
+      rules:
+        - matches:
+            - path:
+                value: "/"
+          backendRefs:
+            - name: service-1
+              port: 8080
+backendTrafficPolicies:
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: envoy-gateway
+      name: policy-for-gateway
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+      http2:
+        initialStreamWindowSize: 2Mi
+        initialConnectionWindowSize: 1Gi
+        maxConcurrentStreams: 500
+        onInvalidMessage: TerminateConnection
+  - apiVersion: gateway.envoyproxy.io/v1alpha1
+    kind: BackendTrafficPolicy
+    metadata:
+      namespace: default
+      name: policy-for-route
+    spec:
+      targetRef:
+        group: gateway.networking.k8s.io
+        kind: HTTPRoute
+        name: httproute-1
+      http2:
+        initialStreamWindowSize: 1Mi
+        initialConnectionWindowSize: 500Mi
+        maxConcurrentStreams: 200
+        onInvalidMessage: TerminateStream
diff --git a/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.out.yaml b/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.out.yaml
new file mode 100644
index 000000000..35dbb164b
--- /dev/null
+++ b/internal/gatewayapi/testdata/backendtrafficpolicy-with-http2.out.yaml
@@ -0,0 +1,336 @@
+backendTrafficPolicies:
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-route
+    namespace: default
+  spec:
+    http2:
+      initialConnectionWindowSize: 500Mi
+      initialStreamWindowSize: 1Mi
+      maxConcurrentStreams: 200
+      onInvalidMessage: TerminateStream
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: HTTPRoute
+      name: httproute-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-2
+        namespace: envoy-gateway
+        sectionName: http
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+- apiVersion: gateway.envoyproxy.io/v1alpha1
+  kind: BackendTrafficPolicy
+  metadata:
+    creationTimestamp: null
+    name: policy-for-gateway
+    namespace: envoy-gateway
+  spec:
+    http2:
+      initialConnectionWindowSize: 1Gi
+      initialStreamWindowSize: 2Mi
+      maxConcurrentStreams: 500
+      onInvalidMessage: TerminateConnection
+    targetRef:
+      group: gateway.networking.k8s.io
+      kind: Gateway
+      name: gateway-1
+  status:
+    ancestors:
+    - ancestorRef:
+        group: gateway.networking.k8s.io
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+      conditions:
+      - lastTransitionTime: null
+        message: Policy has been accepted.
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+gateways:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-1
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: Gateway
+  metadata:
+    creationTimestamp: null
+    name: gateway-2
+    namespace: envoy-gateway
+  spec:
+    gatewayClassName: envoy-gateway-class
+    listeners:
+    - allowedRoutes:
+        namespaces:
+          from: All
+      name: http
+      port: 80
+      protocol: HTTP
+  status:
+    listeners:
+    - attachedRoutes: 1
+      conditions:
+      - lastTransitionTime: null
+        message: Sending translated listener configuration to the data plane
+        reason: Programmed
+        status: "True"
+        type: Programmed
+      - lastTransitionTime: null
+        message: Listener has been successfully translated
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Listener references have been resolved
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      name: http
+      supportedKinds:
+      - group: gateway.networking.k8s.io
+        kind: HTTPRoute
+      - group: gateway.networking.k8s.io
+        kind: GRPCRoute
+grpcRoutes:
+- apiVersion: gateway.networking.k8s.io/v1alpha2
+  kind: GRPCRoute
+  metadata:
+    creationTimestamp: null
+    name: grpcroute-1
+    namespace: default
+  spec:
+    parentRefs:
+    - name: gateway-1
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+httpRoutes:
+- apiVersion: gateway.networking.k8s.io/v1
+  kind: HTTPRoute
+  metadata:
+    creationTimestamp: null
+    name: httproute-1
+    namespace: default
+  spec:
+    hostnames:
+    - gateway.envoyproxy.io
+    parentRefs:
+    - name: gateway-2
+      namespace: envoy-gateway
+      sectionName: http
+    rules:
+    - backendRefs:
+      - name: service-1
+        port: 8080
+      matches:
+      - path:
+          value: /
+  status:
+    parents:
+    - conditions:
+      - lastTransitionTime: null
+        message: Route is accepted
+        reason: Accepted
+        status: "True"
+        type: Accepted
+      - lastTransitionTime: null
+        message: Resolved all the Object references for the Route
+        reason: ResolvedRefs
+        status: "True"
+        type: ResolvedRefs
+      controllerName: gateway.envoyproxy.io/gatewayclass-controller
+      parentRef:
+        name: gateway-2
+        namespace: envoy-gateway
+        sectionName: http
+infraIR:
+  envoy-gateway/gateway-1:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-1/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-1
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-1
+  envoy-gateway/gateway-2:
+    proxy:
+      listeners:
+      - address: null
+        name: envoy-gateway/gateway-2/http
+        ports:
+        - containerPort: 10080
+          name: http-80
+          protocol: HTTP
+          servicePort: 80
+      metadata:
+        labels:
+          gateway.envoyproxy.io/owning-gateway-name: gateway-2
+          gateway.envoyproxy.io/owning-gateway-namespace: envoy-gateway
+      name: envoy-gateway/gateway-2
+xdsIR:
+  envoy-gateway/gateway-1:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: true
+      metadata:
+        kind: Gateway
+        name: gateway-1
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-1/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: grpcroute/default/grpcroute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: GRPC
+            weight: 1
+        hostname: '*'
+        isHTTP2: true
+        metadata:
+          kind: GRPCRoute
+          name: grpcroute-1
+          namespace: default
+        name: grpcroute/default/grpcroute-1/rule/0/match/-1/*
+        traffic:
+          http2:
+            initialConnectionWindowSize: 2097152
+            initialStreamWindowSize: 1073741824
+            maxConcurrentStreams: 500
+            resetStreamOnError: false
+  envoy-gateway/gateway-2:
+    accessLog:
+      text:
+      - path: /dev/stdout
+    http:
+    - address: 0.0.0.0
+      hostnames:
+      - '*'
+      isHTTP2: false
+      metadata:
+        kind: Gateway
+        name: gateway-2
+        namespace: envoy-gateway
+        sectionName: http
+      name: envoy-gateway/gateway-2/http
+      path:
+        escapedSlashesAction: UnescapeAndRedirect
+        mergeSlashes: true
+      port: 10080
+      routes:
+      - destination:
+          name: httproute/default/httproute-1/rule/0
+          settings:
+          - addressType: IP
+            endpoints:
+            - host: 7.7.7.7
+              port: 8080
+            protocol: HTTP
+            weight: 1
+        hostname: gateway.envoyproxy.io
+        isHTTP2: false
+        metadata:
+          kind: HTTPRoute
+          name: httproute-1
+          namespace: default
+        name: httproute/default/httproute-1/rule/0/match/0/gateway_envoyproxy_io
+        pathMatch:
+          distinct: false
+          name: ""
+          prefix: /
+        traffic:
+          http2:
+            initialConnectionWindowSize: 1048576
+            initialStreamWindowSize: 524288000
+            maxConcurrentStreams: 200
+            resetStreamOnError: true
diff --git a/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.yaml b/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.yaml
new file mode 100644
index 000000000..97d0b5457
--- /dev/null
+++ b/internal/xds/translator/testdata/in/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.yaml
@@ -0,0 +1,27 @@
+name: "http-route"
+http:
+- name: "first-listener"
+  address: "0.0.0.0"
+  port: 10080
+  hostnames:
+  - "*"
+  path:
+    mergeSlashes: true
+    escapedSlashesAction: UnescapeAndRedirect
+  routes:
+  - name: "rewrite-route"
+    pathMatch:
+      prefix: "/origin/"
+    hostname: gateway.envoyproxy.io
+    headerMatches:
+    - name: ":authority"
+      exact: gateway.envoyproxy.io
+    destination:
+      name: "rewrite-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+    urlRewrite:
+      path:
+        prefixMatchReplace: /rewrite/
diff --git a/internal/xds/translator/testdata/in/xds-ir/http2-route.yaml b/internal/xds/translator/testdata/in/xds-ir/http2-route.yaml
index f3dc12c5a..5d2710809 100644
--- a/internal/xds/translator/testdata/in/xds-ir/http2-route.yaml
+++ b/internal/xds/translator/testdata/in/xds-ir/http2-route.yaml
@@ -28,3 +28,59 @@ http:
         - host: "1.2.3.4"
           port: 50000
         protocol: GRPC
+    traffic:
+      http2:
+        initialConnectionWindowSize: 1048576
+        initialStreamWindowSize: 524288000
+        maxConcurrentStreams: 200
+        resetStreamOnError: true
+  - name: "second-route"
+    hostname: "*"
+    pathMatch:
+      name: "test"
+      exact: "bar/foo"
+    destination:
+      name: "second-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+        protocol: GRPC
+    traffic:
+      http2: {}
+  - name: "third-route-use-client"
+    hostname: "*"
+    pathMatch:
+      name: "test"
+      exact: "bar/bar"
+    destination:
+      name: "third-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+        protocol: HTTP
+    useClientProtocol: true
+    traffic:
+      http2:
+        initialConnectionWindowSize: 1048576
+        initialStreamWindowSize: 524288000
+        maxConcurrentStreams: 200
+        resetStreamOnError: false
+  - name: "fourth-route-not-http2"
+    hostname: "*"
+    pathMatch:
+      name: "test"
+      exact: "foo/foo"
+    destination:
+      name: "fourth-route-dest"
+      settings:
+      - endpoints:
+        - host: "1.2.3.4"
+          port: 50000
+    traffic:
+      http2:
+        initialConnectionWindowSize: 1048576
+        initialStreamWindowSize: 524288000
+        maxConcurrentStreams: 200
+        resetStreamOnError: true
diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-als-tcp.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-als-tcp.clusters.yaml
index 18b309bb7..9603f302b 100755
--- a/internal/xds/translator/testdata/out/xds-ir/accesslog-als-tcp.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-als-tcp.clusters.yaml
@@ -19,4 +19,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.clusters.yaml
index b8874bf24..6d040000d 100644
--- a/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-cel.clusters.yaml
@@ -46,4 +46,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.clusters.yaml
index cbf453a1f..ea9ef9405 100644
--- a/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-endpoint-stats.clusters.yaml
@@ -50,4 +50,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.clusters.yaml
index b8874bf24..6d040000d 100644
--- a/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-formatters.clusters.yaml
@@ -46,4 +46,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.clusters.yaml
index b8874bf24..6d040000d 100644
--- a/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-multi-cel.clusters.yaml
@@ -46,4 +46,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.clusters.yaml
index 6ba4705c1..d9c561cee 100644
--- a/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/accesslog-without-format.clusters.yaml
@@ -36,7 +36,9 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
 - circuitBreakers:
     thresholds:
     - maxRetries: 1024
@@ -68,4 +70,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/accesslog.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/accesslog.clusters.yaml
index 6ba4705c1..d9c561cee 100644
--- a/internal/xds/translator/testdata/out/xds-ir/accesslog.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/accesslog.clusters.yaml
@@ -36,7 +36,9 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
 - circuitBreakers:
     thresholds:
     - maxRetries: 1024
@@ -68,4 +70,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-auth.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-auth.clusters.yaml
index cf4fbc9b2..e478c2054 100644
--- a/internal/xds/translator/testdata/out/xds-ir/ext-auth.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ext-auth.clusters.yaml
@@ -70,7 +70,9 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
 - circuitBreakers:
     thresholds:
     - maxRetries: 1024
diff --git a/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml
index 08b38495e..6ea0615cb 100755
--- a/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ext-proc.clusters.yaml
@@ -53,7 +53,9 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
 - circuitBreakers:
     thresholds:
     - maxRetries: 1024
@@ -75,7 +77,9 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
 - circuitBreakers:
     thresholds:
     - maxRetries: 1024
@@ -97,7 +101,9 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
 - circuitBreakers:
     thresholds:
     - maxRetries: 1024
@@ -119,4 +125,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-preserve-client-protocol.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-preserve-client-protocol.clusters.yaml
index 5de1d1cce..f8cfa834c 100644
--- a/internal/xds/translator/testdata/out/xds-ir/http-preserve-client-protocol.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/http-preserve-client-protocol.clusters.yaml
@@ -19,5 +19,7 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       useDownstreamProtocolConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
         httpProtocolOptions: {}
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.clusters.yaml
new file mode 100644
index 000000000..027db39fb
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.clusters.yaml
@@ -0,0 +1,17 @@
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: rewrite-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: rewrite-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.endpoints.yaml
new file mode 100644
index 000000000..256dda090
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.endpoints.yaml
@@ -0,0 +1,12 @@
+- clusterName: rewrite-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: rewrite-route-dest/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.listeners.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.listeners.yaml
new file mode 100644
index 000000000..586f3fa82
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.listeners.yaml
@@ -0,0 +1,35 @@
+- address:
+    socketAddress:
+      address: 0.0.0.0
+      portValue: 10080
+  defaultFilterChain:
+    filters:
+    - name: envoy.filters.network.http_connection_manager
+      typedConfig:
+        '@type': type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
+        commonHttpProtocolOptions:
+          headersWithUnderscoresAction: REJECT_REQUEST
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+          maxConcurrentStreams: 100
+        httpFilters:
+        - name: envoy.filters.http.router
+          typedConfig:
+            '@type': type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
+            suppressEnvoyHeaders: true
+        mergeSlashes: true
+        normalizePath: true
+        pathWithEscapedSlashesAction: UNESCAPE_AND_REDIRECT
+        rds:
+          configSource:
+            ads: {}
+            resourceApiVersion: V3
+          routeConfigName: first-listener
+        serverHeaderTransformation: PASS_THROUGH
+        statPrefix: http-10080
+        useRemoteAddress: true
+    name: first-listener
+  drainType: MODIFY_ONLY
+  name: first-listener
+  perConnectionBufferLimitBytes: 32768
diff --git a/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.routes.yaml
new file mode 100644
index 000000000..84bc70f04
--- /dev/null
+++ b/internal/xds/translator/testdata/out/xds-ir/http-route-rewrite-sufixx-with-slash-url-prefix.routes.yaml
@@ -0,0 +1,19 @@
+- ignorePortInHostMatching: true
+  name: first-listener
+  virtualHosts:
+  - domains:
+    - gateway.envoyproxy.io
+    name: first-listener/gateway_envoyproxy_io
+    routes:
+    - match:
+        headers:
+        - name: :authority
+          stringMatch:
+            exact: gateway.envoyproxy.io
+        pathSeparatedPrefix: /origin
+      name: rewrite-route
+      route:
+        cluster: rewrite-route-dest
+        prefixRewrite: /rewrite
+        upgradeConfigs:
+        - upgradeType: websocket
diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-route.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-route.clusters.yaml
index 6086cfbb8..05cf41776 100644
--- a/internal/xds/translator/testdata/out/xds-ir/http2-route.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/http2-route.clusters.yaml
@@ -19,4 +19,76 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 524288000
+          initialStreamWindowSize: 1048576
+          maxConcurrentStreams: 200
+          overrideStreamErrorOnInvalidHttpMessage: true
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: second-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: second-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+  typedExtensionProtocolOptions:
+    envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+      '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+      explicitHttpConfig:
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: third-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: third-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
+  typedExtensionProtocolOptions:
+    envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
+      '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
+      useDownstreamProtocolConfig:
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 524288000
+          initialStreamWindowSize: 1048576
+          maxConcurrentStreams: 200
+          overrideStreamErrorOnInvalidHttpMessage: false
+        httpProtocolOptions: {}
+- circuitBreakers:
+    thresholds:
+    - maxRetries: 1024
+  commonLbConfig:
+    localityWeightedLbConfig: {}
+  connectTimeout: 10s
+  dnsLookupFamily: V4_ONLY
+  edsClusterConfig:
+    edsConfig:
+      ads: {}
+      resourceApiVersion: V3
+    serviceName: fourth-route-dest
+  lbPolicy: LEAST_REQUEST
+  name: fourth-route-dest
+  outlierDetection: {}
+  perConnectionBufferLimitBytes: 32768
+  type: EDS
diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-route.endpoints.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-route.endpoints.yaml
index 3b3f2d090..f185af17d 100644
--- a/internal/xds/translator/testdata/out/xds-ir/http2-route.endpoints.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/http2-route.endpoints.yaml
@@ -10,3 +10,39 @@
     loadBalancingWeight: 1
     locality:
       region: first-route-dest/backend/0
+- clusterName: second-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: second-route-dest/backend/0
+- clusterName: third-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: third-route-dest/backend/0
+- clusterName: fourth-route-dest
+  endpoints:
+  - lbEndpoints:
+    - endpoint:
+        address:
+          socketAddress:
+            address: 1.2.3.4
+            portValue: 50000
+      loadBalancingWeight: 1
+    loadBalancingWeight: 1
+    locality:
+      region: fourth-route-dest/backend/0
diff --git a/internal/xds/translator/testdata/out/xds-ir/http2-route.routes.yaml b/internal/xds/translator/testdata/out/xds-ir/http2-route.routes.yaml
index a32852fcd..4fdedfabf 100644
--- a/internal/xds/translator/testdata/out/xds-ir/http2-route.routes.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/http2-route.routes.yaml
@@ -20,3 +20,24 @@
         cluster: first-route-dest
         upgradeConfigs:
         - upgradeType: websocket
+    - match:
+        path: bar/foo
+      name: second-route
+      route:
+        cluster: second-route-dest
+        upgradeConfigs:
+        - upgradeType: websocket
+    - match:
+        path: bar/bar
+      name: third-route-use-client
+      route:
+        cluster: third-route-dest
+        upgradeConfigs:
+        - upgradeType: websocket
+    - match:
+        path: foo/foo
+      name: fourth-route-not-http2
+      route:
+        cluster: fourth-route-dest
+        upgradeConfigs:
+        - upgradeType: websocket
diff --git a/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.clusters.yaml
index d4e9db34e..e75a68919 100644
--- a/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/jwt-ratelimit.clusters.yaml
@@ -119,4 +119,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-custom-domain.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-custom-domain.clusters.yaml
index bd23f61ee..45e8e0898 100644
--- a/internal/xds/translator/testdata/out/xds-ir/ratelimit-custom-domain.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-custom-domain.clusters.yaml
@@ -93,4 +93,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.clusters.yaml
index 9f9bc9f1d..0ba174907 100644
--- a/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-disable-headers.clusters.yaml
@@ -93,4 +93,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-endpoint-stats.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-endpoint-stats.clusters.yaml
index 21ea06816..a3c9b6623 100644
--- a/internal/xds/translator/testdata/out/xds-ir/ratelimit-endpoint-stats.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-endpoint-stats.clusters.yaml
@@ -101,4 +101,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit-sourceip.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit-sourceip.clusters.yaml
index 33fc01336..427f6d153 100644
--- a/internal/xds/translator/testdata/out/xds-ir/ratelimit-sourceip.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit-sourceip.clusters.yaml
@@ -110,4 +110,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/ratelimit.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/ratelimit.clusters.yaml
index 9f9bc9f1d..0ba174907 100644
--- a/internal/xds/translator/testdata/out/xds-ir/ratelimit.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/ratelimit.clusters.yaml
@@ -93,4 +93,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.clusters.yaml
index 0936f4b45..9e7469dd2 100644
--- a/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/tracing-endpoint-stats.clusters.yaml
@@ -50,4 +50,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/internal/xds/translator/testdata/out/xds-ir/tracing.clusters.yaml b/internal/xds/translator/testdata/out/xds-ir/tracing.clusters.yaml
index 35bc81158..dce48b2c0 100644
--- a/internal/xds/translator/testdata/out/xds-ir/tracing.clusters.yaml
+++ b/internal/xds/translator/testdata/out/xds-ir/tracing.clusters.yaml
@@ -46,4 +46,6 @@
     envoy.extensions.upstreams.http.v3.HttpProtocolOptions:
       '@type': type.googleapis.com/envoy.extensions.upstreams.http.v3.HttpProtocolOptions
       explicitHttpConfig:
-        http2ProtocolOptions: {}
+        http2ProtocolOptions:
+          initialConnectionWindowSize: 1048576
+          initialStreamWindowSize: 65536
diff --git a/site/content/en/latest/api/extension_types.md b/site/content/en/latest/api/extension_types.md
index b0e858331..a9f4f2bdb 100644
--- a/site/content/en/latest/api/extension_types.md
+++ b/site/content/en/latest/api/extension_types.md
@@ -461,6 +461,7 @@ _Appears in:_
 | `timeout` | _[Timeout](#timeout)_ |  false  | Timeout settings for the backend connections. |
 | `connection` | _[BackendConnection](#backendconnection)_ |  false  | Connection includes backend connection settings. |
 | `dns` | _[DNS](#dns)_ |  false  | DNS includes dns resolution settings. |
+| `http2` | _[HTTP2Settings](#http2settings)_ |  false  | HTTP2 provides HTTP/2 configuration for backend connections. |
 
 
 #### BasicAuth
@@ -1789,9 +1790,10 @@ _Appears in:_
 
 
 
-HTTP2Settings provides HTTP/2 configuration on the listener.
+HTTP2Settings provides HTTP/2 configuration for listeners and backends.
 
 _Appears in:_
+- [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
 - [ClientTrafficPolicySpec](#clienttrafficpolicyspec)
 
 | Field | Type | Required | Description |
@@ -1799,6 +1801,7 @@ _Appears in:_
 | `initialStreamWindowSize` | _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#quantity-resource-api)_ |  false  | InitialStreamWindowSize sets the initial window size for HTTP/2 streams.<br />If not set, the default value is 64 KiB(64*1024). |
 | `initialConnectionWindowSize` | _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#quantity-resource-api)_ |  false  | InitialConnectionWindowSize sets the initial window size for HTTP/2 connections.<br />If not set, the default value is 1 MiB. |
 | `maxConcurrentStreams` | _integer_ |  false  | MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection.<br />If not set, the default value is 100. |
+| `onInvalidMessage` | _[InvalidMessageAction](#invalidmessageaction)_ |  false  | OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error<br />It's recommended for L2 Envoy deployments to set this value to TerminateStream.<br />https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two<br />Default: TerminateConnection |
 
 
 #### HTTP3Settings
@@ -2047,6 +2050,21 @@ _Appears in:_
 | `Host` | InfrastructureProviderTypeHost defines the "Host" provider.<br /> | 
 
 
+#### InvalidMessageAction
+
+_Underlying type:_ _string_
+
+
+
+_Appears in:_
+- [HTTP2Settings](#http2settings)
+
+| Value | Description |
+| ----- | ----------- |
+| `TerminateConnection` |  | 
+| `TerminateStream` |  | 
+
+
 #### JSONPatchOperation
 
 
diff --git a/site/content/zh/latest/api/extension_types.md b/site/content/zh/latest/api/extension_types.md
index b0e858331..a9f4f2bdb 100644
--- a/site/content/zh/latest/api/extension_types.md
+++ b/site/content/zh/latest/api/extension_types.md
@@ -461,6 +461,7 @@ _Appears in:_
 | `timeout` | _[Timeout](#timeout)_ |  false  | Timeout settings for the backend connections. |
 | `connection` | _[BackendConnection](#backendconnection)_ |  false  | Connection includes backend connection settings. |
 | `dns` | _[DNS](#dns)_ |  false  | DNS includes dns resolution settings. |
+| `http2` | _[HTTP2Settings](#http2settings)_ |  false  | HTTP2 provides HTTP/2 configuration for backend connections. |
 
 
 #### BasicAuth
@@ -1789,9 +1790,10 @@ _Appears in:_
 
 
 
-HTTP2Settings provides HTTP/2 configuration on the listener.
+HTTP2Settings provides HTTP/2 configuration for listeners and backends.
 
 _Appears in:_
+- [BackendTrafficPolicySpec](#backendtrafficpolicyspec)
 - [ClientTrafficPolicySpec](#clienttrafficpolicyspec)
 
 | Field | Type | Required | Description |
@@ -1799,6 +1801,7 @@ _Appears in:_
 | `initialStreamWindowSize` | _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#quantity-resource-api)_ |  false  | InitialStreamWindowSize sets the initial window size for HTTP/2 streams.<br />If not set, the default value is 64 KiB(64*1024). |
 | `initialConnectionWindowSize` | _[Quantity](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.29/#quantity-resource-api)_ |  false  | InitialConnectionWindowSize sets the initial window size for HTTP/2 connections.<br />If not set, the default value is 1 MiB. |
 | `maxConcurrentStreams` | _integer_ |  false  | MaxConcurrentStreams sets the maximum number of concurrent streams allowed per connection.<br />If not set, the default value is 100. |
+| `onInvalidMessage` | _[InvalidMessageAction](#invalidmessageaction)_ |  false  | OnInvalidMessage determines if Envoy will terminate the connection or just the offending stream in the event of HTTP messaging error<br />It's recommended for L2 Envoy deployments to set this value to TerminateStream.<br />https://www.envoyproxy.io/docs/envoy/latest/configuration/best_practices/level_two<br />Default: TerminateConnection |
 
 
 #### HTTP3Settings
@@ -2047,6 +2050,21 @@ _Appears in:_
 | `Host` | InfrastructureProviderTypeHost defines the "Host" provider.<br /> | 
 
 
+#### InvalidMessageAction
+
+_Underlying type:_ _string_
+
+
+
+_Appears in:_
+- [HTTP2Settings](#http2settings)
+
+| Value | Description |
+| ----- | ----------- |
+| `TerminateConnection` |  | 
+| `TerminateStream` |  | 
+
+
 #### JSONPatchOperation