From 81e1f75b628e0b2e097ddc1c8029baf973b53d60 Mon Sep 17 00:00:00 2001 From: Michael Lumish Date: Thu, 1 Jun 2023 17:16:24 -0700 Subject: [PATCH] grpc-js-xds: Support string_match in header matching --- packages/grpc-js-xds/src/matcher.ts | 48 +++++++++++++++---- packages/grpc-js-xds/src/resolver-xds.ts | 28 +++++++++-- .../src/xds-stream-state/rds-state.ts | 3 +- 3 files changed, 65 insertions(+), 14 deletions(-) diff --git a/packages/grpc-js-xds/src/matcher.ts b/packages/grpc-js-xds/src/matcher.ts index b173c30e..148df7f8 100644 --- a/packages/grpc-js-xds/src/matcher.ts +++ b/packages/grpc-js-xds/src/matcher.ts @@ -37,14 +37,19 @@ export interface ValueMatcher { } export class ExactValueMatcher implements ValueMatcher { - constructor(private targetValue: string) {} + constructor(private targetValue: string, private ignoreCase: boolean) { + } apply(value: string) { - return value === this.targetValue; + if (this.ignoreCase) { + return value.toLowerCase() === this.targetValue.toLowerCase(); + } else { + return value === this.targetValue; + } } toString() { - return 'Exact(' + this.targetValue + ')'; + return 'Exact(' + this.targetValue + ', ignore_case=' + this.ignoreCase + ')'; } } @@ -94,26 +99,51 @@ export class PresentValueMatcher implements ValueMatcher { } export class PrefixValueMatcher implements ValueMatcher { - constructor(private prefix: string) {} + constructor(private prefix: string, private ignoreCase: boolean) { + } apply(value: string) { - return value.startsWith(this.prefix); + if (this.ignoreCase) { + return value.toLowerCase().startsWith(this.prefix.toLowerCase()); + } else { + return value.startsWith(this.prefix); + } } toString() { - return 'Prefix(' + this.prefix + ')'; + return 'Prefix(' + this.prefix + ', ignore_case=' + this.ignoreCase + ')'; } } export class SuffixValueMatcher implements ValueMatcher { - constructor(private suffix: string) {} + constructor(private suffix: string, private ignoreCase: boolean) {} apply(value: string) { - return value.endsWith(this.suffix); + if (this.ignoreCase) { + return value.toLowerCase().endsWith(this.suffix.toLowerCase()); + } else { + return value.endsWith(this.suffix); + } } toString() { - return 'Suffix(' + this.suffix + ')'; + return 'Suffix(' + this.suffix + ', ignore_case=' + this.ignoreCase + ')'; + } +} + +export class ContainsValueMatcher implements ValueMatcher { + constructor(private contains: string, private ignoreCase: boolean) {} + + apply(value: string) { + if (this.ignoreCase) { + return value.toLowerCase().includes(this.contains.toLowerCase()); + } else { + return value.includes(this.contains); + } + } + + toString() { + return 'Contains(' + this.contains + + ', ignore_case=' + this.ignoreCase + ')'; } } diff --git a/packages/grpc-js-xds/src/resolver-xds.ts b/packages/grpc-js-xds/src/resolver-xds.ts index 9879a2c6..6c2ba432 100644 --- a/packages/grpc-js-xds/src/resolver-xds.ts +++ b/packages/grpc-js-xds/src/resolver-xds.ts @@ -37,7 +37,7 @@ import { HeaderMatcher__Output } from './generated/envoy/config/route/v3/HeaderM import ConfigSelector = experimental.ConfigSelector; import LoadBalancingConfig = experimental.LoadBalancingConfig; import { XdsClusterManagerLoadBalancingConfig } from './load-balancer-xds-cluster-manager'; -import { ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; +import { ContainsValueMatcher, ExactValueMatcher, FullMatcher, HeaderMatcher, Matcher, PathExactValueMatcher, PathPrefixValueMatcher, PathSafeRegexValueMatcher, PrefixValueMatcher, PresentValueMatcher, RangeValueMatcher, RejectValueMatcher, SafeRegexValueMatcher, SuffixValueMatcher, ValueMatcher } from './matcher'; import { envoyFractionToFraction, Fraction } from "./fraction"; import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedClusterRouteAction } from './route-action'; import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL } from './resources'; @@ -136,7 +136,7 @@ function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Match let valueChecker: ValueMatcher; switch (headerMatch.header_match_specifier) { case 'exact_match': - valueChecker = new ExactValueMatcher(headerMatch.exact_match!); + valueChecker = new ExactValueMatcher(headerMatch.exact_match!, false); break; case 'safe_regex_match': valueChecker = new SafeRegexValueMatcher(headerMatch.safe_regex_match!.regex); @@ -150,10 +150,30 @@ function getPredicateForHeaderMatcher(headerMatch: HeaderMatcher__Output): Match valueChecker = new PresentValueMatcher(); break; case 'prefix_match': - valueChecker = new PrefixValueMatcher(headerMatch.prefix_match!); + valueChecker = new PrefixValueMatcher(headerMatch.prefix_match!, false); break; case 'suffix_match': - valueChecker = new SuffixValueMatcher(headerMatch.suffix_match!); + valueChecker = new SuffixValueMatcher(headerMatch.suffix_match!, false); + break; + case 'string_match': + const stringMatch = headerMatch.string_match! + switch (stringMatch.match_pattern) { + case 'exact': + valueChecker = new ExactValueMatcher(stringMatch.exact!, stringMatch.ignore_case); + break; + case 'safe_regex': + valueChecker = new SafeRegexValueMatcher(stringMatch.safe_regex!.regex); + break; + case 'prefix': + valueChecker = new PrefixValueMatcher(stringMatch.prefix!, stringMatch.ignore_case); + break; + case 'suffix': + valueChecker = new SuffixValueMatcher(stringMatch.suffix!, stringMatch.ignore_case); + break; + case 'contains': + valueChecker = new ContainsValueMatcher(stringMatch.contains!, stringMatch.ignore_case); + break; + } break; default: valueChecker = new RejectValueMatcher(); diff --git a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts index fef69451..57e25edd 100644 --- a/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts +++ b/packages/grpc-js-xds/src/xds-stream-state/rds-state.ts @@ -29,7 +29,8 @@ const SUPPPORTED_HEADER_MATCH_SPECIFIERS = [ 'range_match', 'present_match', 'prefix_match', - 'suffix_match']; + 'suffix_match', + 'string_match']; const SUPPORTED_CLUSTER_SPECIFIERS = ['cluster', 'weighted_clusters', 'cluster_header']; const UINT32_MAX = 0xFFFFFFFF;