mirror of https://github.com/grpc/grpc-node.git
grpc-js-xds: Add HTTP Filters support
This commit is contained in:
parent
49822c8f49
commit
311aca31e4
|
@ -12,7 +12,7 @@
|
|||
"prepare": "npm run compile",
|
||||
"pretest": "npm run compile",
|
||||
"posttest": "npm run check",
|
||||
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto",
|
||||
"generate-types": "proto-loader-gen-types --keepCase --longs String --enums String --defaults --oneofs --includeComments --includeDirs deps/envoy-api/ deps/udpa/ deps/googleapis/ deps/protoc-gen-validate/ -O src/generated/ --grpcLib @grpc/grpc-js envoy/service/discovery/v2/ads.proto envoy/service/load_stats/v2/lrs.proto envoy/service/discovery/v3/ads.proto envoy/service/load_stats/v3/lrs.proto envoy/config/listener/v3/listener.proto envoy/config/route/v3/route.proto envoy/config/cluster/v3/cluster.proto envoy/config/endpoint/v3/endpoint.proto envoy/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto udpa/type/v1/typed_struct.proto",
|
||||
"generate-interop-types": "proto-loader-gen-types --keep-case --longs String --enums String --defaults --oneofs --json --includeComments --includeDirs proto/ -O interop/generated --grpcLib @grpc/grpc-js grpc/testing/test.proto"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -13,4 +13,6 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
*/
|
||||
|
||||
export const EXPERIMENTAL_FAULT_INJECTION = process.env.GRPC_XDS_EXPERIMENTAL_FAULT_INJECTION;
|
|
@ -1,18 +1,15 @@
|
|||
// Original file: null
|
||||
|
||||
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
|
||||
import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation';
|
||||
|
||||
export interface EnumOptions {
|
||||
'allowAlias'?: (boolean);
|
||||
'deprecated'?: (boolean);
|
||||
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
|
||||
'.udpa.annotations.enum_migrate'?: (_udpa_annotations_MigrateAnnotation);
|
||||
}
|
||||
|
||||
export interface EnumOptions__Output {
|
||||
'allowAlias': (boolean);
|
||||
'deprecated': (boolean);
|
||||
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
|
||||
'.udpa.annotations.enum_migrate'?: (_udpa_annotations_MigrateAnnotation__Output);
|
||||
}
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
// Original file: null
|
||||
|
||||
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
|
||||
import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation';
|
||||
|
||||
export interface EnumValueOptions {
|
||||
'deprecated'?: (boolean);
|
||||
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
|
||||
'.envoy.annotations.disallowed_by_default_enum'?: (boolean);
|
||||
'.udpa.annotations.enum_value_migrate'?: (_udpa_annotations_MigrateAnnotation);
|
||||
}
|
||||
|
||||
export interface EnumValueOptions__Output {
|
||||
'deprecated': (boolean);
|
||||
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
|
||||
'.envoy.annotations.disallowed_by_default_enum': (boolean);
|
||||
'.udpa.annotations.enum_value_migrate'?: (_udpa_annotations_MigrateAnnotation__Output);
|
||||
}
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
|
||||
import type { FieldRules as _validate_FieldRules, FieldRules__Output as _validate_FieldRules__Output } from '../../validate/FieldRules';
|
||||
import type { FieldSecurityAnnotation as _udpa_annotations_FieldSecurityAnnotation, FieldSecurityAnnotation__Output as _udpa_annotations_FieldSecurityAnnotation__Output } from '../../udpa/annotations/FieldSecurityAnnotation';
|
||||
import type { FieldMigrateAnnotation as _udpa_annotations_FieldMigrateAnnotation, FieldMigrateAnnotation__Output as _udpa_annotations_FieldMigrateAnnotation__Output } from '../../udpa/annotations/FieldMigrateAnnotation';
|
||||
|
||||
// Original file: null
|
||||
|
||||
|
@ -30,10 +28,6 @@ export interface FieldOptions {
|
|||
'weak'?: (boolean);
|
||||
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
|
||||
'.validate.rules'?: (_validate_FieldRules);
|
||||
'.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation);
|
||||
'.udpa.annotations.sensitive'?: (boolean);
|
||||
'.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation);
|
||||
'.envoy.annotations.disallowed_by_default'?: (boolean);
|
||||
}
|
||||
|
||||
export interface FieldOptions__Output {
|
||||
|
@ -45,8 +39,4 @@ export interface FieldOptions__Output {
|
|||
'weak': (boolean);
|
||||
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
|
||||
'.validate.rules'?: (_validate_FieldRules__Output);
|
||||
'.udpa.annotations.security'?: (_udpa_annotations_FieldSecurityAnnotation__Output);
|
||||
'.udpa.annotations.sensitive': (boolean);
|
||||
'.udpa.annotations.field_migrate'?: (_udpa_annotations_FieldMigrateAnnotation__Output);
|
||||
'.envoy.annotations.disallowed_by_default': (boolean);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Original file: null
|
||||
|
||||
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
|
||||
import type { FileMigrateAnnotation as _udpa_annotations_FileMigrateAnnotation, FileMigrateAnnotation__Output as _udpa_annotations_FileMigrateAnnotation__Output } from '../../udpa/annotations/FileMigrateAnnotation';
|
||||
import type { StatusAnnotation as _udpa_annotations_StatusAnnotation, StatusAnnotation__Output as _udpa_annotations_StatusAnnotation__Output } from '../../udpa/annotations/StatusAnnotation';
|
||||
|
||||
// Original file: null
|
||||
|
||||
|
@ -28,8 +26,6 @@ export interface FileOptions {
|
|||
'objcClassPrefix'?: (string);
|
||||
'csharpNamespace'?: (string);
|
||||
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
|
||||
'.udpa.annotations.file_migrate'?: (_udpa_annotations_FileMigrateAnnotation);
|
||||
'.udpa.annotations.file_status'?: (_udpa_annotations_StatusAnnotation);
|
||||
}
|
||||
|
||||
export interface FileOptions__Output {
|
||||
|
@ -48,6 +44,4 @@ export interface FileOptions__Output {
|
|||
'objcClassPrefix': (string);
|
||||
'csharpNamespace': (string);
|
||||
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
|
||||
'.udpa.annotations.file_migrate'?: (_udpa_annotations_FileMigrateAnnotation__Output);
|
||||
'.udpa.annotations.file_status'?: (_udpa_annotations_StatusAnnotation__Output);
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
// Original file: null
|
||||
|
||||
import type { UninterpretedOption as _google_protobuf_UninterpretedOption, UninterpretedOption__Output as _google_protobuf_UninterpretedOption__Output } from '../../google/protobuf/UninterpretedOption';
|
||||
import type { VersioningAnnotation as _udpa_annotations_VersioningAnnotation, VersioningAnnotation__Output as _udpa_annotations_VersioningAnnotation__Output } from '../../udpa/annotations/VersioningAnnotation';
|
||||
import type { MigrateAnnotation as _udpa_annotations_MigrateAnnotation, MigrateAnnotation__Output as _udpa_annotations_MigrateAnnotation__Output } from '../../udpa/annotations/MigrateAnnotation';
|
||||
|
||||
export interface MessageOptions {
|
||||
'messageSetWireFormat'?: (boolean);
|
||||
|
@ -11,8 +9,6 @@ export interface MessageOptions {
|
|||
'mapEntry'?: (boolean);
|
||||
'uninterpretedOption'?: (_google_protobuf_UninterpretedOption)[];
|
||||
'.validate.disabled'?: (boolean);
|
||||
'.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation);
|
||||
'.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation);
|
||||
}
|
||||
|
||||
export interface MessageOptions__Output {
|
||||
|
@ -22,6 +18,4 @@ export interface MessageOptions__Output {
|
|||
'mapEntry': (boolean);
|
||||
'uninterpretedOption': (_google_protobuf_UninterpretedOption__Output)[];
|
||||
'.validate.disabled': (boolean);
|
||||
'.udpa.annotations.versioning'?: (_udpa_annotations_VersioningAnnotation__Output);
|
||||
'.udpa.annotations.message_migrate'?: (_udpa_annotations_MigrateAnnotation__Output);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
import type * as grpc from '@grpc/grpc-js';
|
||||
import type { ServiceDefinition, EnumTypeDefinition, MessageTypeDefinition } from '@grpc/proto-loader';
|
||||
|
||||
|
||||
type SubtypeConstructor<Constructor extends new (...args: any) => any, Subtype> = {
|
||||
new(...args: ConstructorParameters<Constructor>): Subtype;
|
||||
};
|
||||
|
||||
export interface ProtoGrpcType {
|
||||
google: {
|
||||
protobuf: {
|
||||
DescriptorProto: MessageTypeDefinition
|
||||
Duration: MessageTypeDefinition
|
||||
EnumDescriptorProto: MessageTypeDefinition
|
||||
EnumOptions: MessageTypeDefinition
|
||||
EnumValueDescriptorProto: MessageTypeDefinition
|
||||
EnumValueOptions: MessageTypeDefinition
|
||||
FieldDescriptorProto: MessageTypeDefinition
|
||||
FieldOptions: MessageTypeDefinition
|
||||
FileDescriptorProto: MessageTypeDefinition
|
||||
FileDescriptorSet: MessageTypeDefinition
|
||||
FileOptions: MessageTypeDefinition
|
||||
GeneratedCodeInfo: MessageTypeDefinition
|
||||
ListValue: MessageTypeDefinition
|
||||
MessageOptions: MessageTypeDefinition
|
||||
MethodDescriptorProto: MessageTypeDefinition
|
||||
MethodOptions: MessageTypeDefinition
|
||||
NullValue: EnumTypeDefinition
|
||||
OneofDescriptorProto: MessageTypeDefinition
|
||||
OneofOptions: MessageTypeDefinition
|
||||
ServiceDescriptorProto: MessageTypeDefinition
|
||||
ServiceOptions: MessageTypeDefinition
|
||||
SourceCodeInfo: MessageTypeDefinition
|
||||
Struct: MessageTypeDefinition
|
||||
Timestamp: MessageTypeDefinition
|
||||
UninterpretedOption: MessageTypeDefinition
|
||||
Value: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
udpa: {
|
||||
type: {
|
||||
v1: {
|
||||
TypedStruct: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
}
|
||||
validate: {
|
||||
AnyRules: MessageTypeDefinition
|
||||
BoolRules: MessageTypeDefinition
|
||||
BytesRules: MessageTypeDefinition
|
||||
DoubleRules: MessageTypeDefinition
|
||||
DurationRules: MessageTypeDefinition
|
||||
EnumRules: MessageTypeDefinition
|
||||
FieldRules: MessageTypeDefinition
|
||||
Fixed32Rules: MessageTypeDefinition
|
||||
Fixed64Rules: MessageTypeDefinition
|
||||
FloatRules: MessageTypeDefinition
|
||||
Int32Rules: MessageTypeDefinition
|
||||
Int64Rules: MessageTypeDefinition
|
||||
KnownRegex: EnumTypeDefinition
|
||||
MapRules: MessageTypeDefinition
|
||||
MessageRules: MessageTypeDefinition
|
||||
RepeatedRules: MessageTypeDefinition
|
||||
SFixed32Rules: MessageTypeDefinition
|
||||
SFixed64Rules: MessageTypeDefinition
|
||||
SInt32Rules: MessageTypeDefinition
|
||||
SInt64Rules: MessageTypeDefinition
|
||||
StringRules: MessageTypeDefinition
|
||||
TimestampRules: MessageTypeDefinition
|
||||
UInt32Rules: MessageTypeDefinition
|
||||
UInt64Rules: MessageTypeDefinition
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// Original file: deps/udpa/udpa/type/v1/typed_struct.proto
|
||||
|
||||
import type { Struct as _google_protobuf_Struct, Struct__Output as _google_protobuf_Struct__Output } from '../../../google/protobuf/Struct';
|
||||
|
||||
/**
|
||||
* A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that
|
||||
* describes the type of the serialized message. This is very similar to google.protobuf.Any,
|
||||
* instead of having protocol buffer binary, this employs google.protobuf.Struct as value.
|
||||
*
|
||||
* This message is intended to be embedded inside Any, so it shouldn't be directly referred
|
||||
* from other UDPA messages.
|
||||
*
|
||||
* When packing an opaque extension config, packing the expected type into Any is preferred
|
||||
* wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor
|
||||
* is not available, for example if:
|
||||
* - A control plane sends opaque message that is originally from external source in human readable
|
||||
* format such as JSON or YAML.
|
||||
* - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot
|
||||
* serialize the message in protocol buffer binary format.
|
||||
* - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension
|
||||
* uses. This has to be indicated in the DPLB capability negotiation.
|
||||
*
|
||||
* When a DPLB receives a TypedStruct in Any, it should:
|
||||
* - Check if the type_url of the TypedStruct matches the type the extension expects.
|
||||
* - Convert value to the type described in type_url and perform validation.
|
||||
* TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link
|
||||
* protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions).
|
||||
*/
|
||||
export interface TypedStruct {
|
||||
/**
|
||||
* A URL that uniquely identifies the type of the serialize protocol buffer message.
|
||||
* This has same semantics and format described in google.protobuf.Any:
|
||||
* https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto
|
||||
*/
|
||||
'type_url'?: (string);
|
||||
/**
|
||||
* A JSON representation of the above specified type.
|
||||
*/
|
||||
'value'?: (_google_protobuf_Struct);
|
||||
}
|
||||
|
||||
/**
|
||||
* A TypedStruct contains an arbitrary JSON serialized protocol buffer message with a URL that
|
||||
* describes the type of the serialized message. This is very similar to google.protobuf.Any,
|
||||
* instead of having protocol buffer binary, this employs google.protobuf.Struct as value.
|
||||
*
|
||||
* This message is intended to be embedded inside Any, so it shouldn't be directly referred
|
||||
* from other UDPA messages.
|
||||
*
|
||||
* When packing an opaque extension config, packing the expected type into Any is preferred
|
||||
* wherever possible for its efficiency. TypedStruct should be used only if a proto descriptor
|
||||
* is not available, for example if:
|
||||
* - A control plane sends opaque message that is originally from external source in human readable
|
||||
* format such as JSON or YAML.
|
||||
* - The control plane doesn't have the knowledge of the protocol buffer schema hence it cannot
|
||||
* serialize the message in protocol buffer binary format.
|
||||
* - The DPLB doesn't have have the knowledge of the protocol buffer schema its plugin or extension
|
||||
* uses. This has to be indicated in the DPLB capability negotiation.
|
||||
*
|
||||
* When a DPLB receives a TypedStruct in Any, it should:
|
||||
* - Check if the type_url of the TypedStruct matches the type the extension expects.
|
||||
* - Convert value to the type described in type_url and perform validation.
|
||||
* TODO(lizan): Figure out how TypeStruct should be used with DPLB extensions that doesn't link
|
||||
* protobuf descriptor with DPLB itself, (e.g. gRPC LB Plugin, Envoy WASM extensions).
|
||||
*/
|
||||
export interface TypedStruct__Output {
|
||||
/**
|
||||
* A URL that uniquely identifies the type of the serialize protocol buffer message.
|
||||
* This has same semantics and format described in google.protobuf.Any:
|
||||
* https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/any.proto
|
||||
*/
|
||||
'type_url': (string);
|
||||
/**
|
||||
* A JSON representation of the above specified type.
|
||||
*/
|
||||
'value'?: (_google_protobuf_Struct__Output);
|
||||
}
|
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Copyright 2021 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This is a non-public, unstable API, but it's very convenient
|
||||
import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
|
||||
import { experimental } from '@grpc/grpc-js';
|
||||
import { Any__Output } from './generated/google/protobuf/Any';
|
||||
import Filter = experimental.Filter;
|
||||
import FilterFactory = experimental.FilterFactory;
|
||||
import { TypedStruct__Output } from './generated/udpa/type/v1/TypedStruct';
|
||||
import { FilterConfig__Output } from './generated/envoy/config/route/v3/FilterConfig';
|
||||
import { HttpFilter__Output } from './generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpFilter';
|
||||
|
||||
const TYPED_STRUCT_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct';
|
||||
const TYPED_STRUCT_NAME = 'udpa.type.v1.TypedStruct';
|
||||
|
||||
const FILTER_CONFIG_URL = 'type.googleapis.com/envoy.config.route.v3.FilterConfig';
|
||||
const FILTER_CONFIG_NAME = 'envoy.config.route.v3.FilterConfig';
|
||||
|
||||
const resourceRoot = loadProtosWithOptionsSync([
|
||||
'udpa/type/v1/typed_struct.proto',
|
||||
'envoy/config/route/v3/route_components.proto'], {
|
||||
keepCase: true,
|
||||
includeDirs: [
|
||||
// Paths are relative to src/build
|
||||
__dirname + '/../../deps/udpa/',
|
||||
__dirname + '/../../deps/envoy-api/'
|
||||
],
|
||||
}
|
||||
);
|
||||
|
||||
export interface HttpFilterConfig {
|
||||
typeUrl: string;
|
||||
config: any;
|
||||
}
|
||||
|
||||
export interface HttpFilterFactoryConstructor<FilterType extends Filter> {
|
||||
new(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory<FilterType>;
|
||||
}
|
||||
|
||||
export interface HttpFilterRegistryEntry {
|
||||
parseTopLevelFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null;
|
||||
parseOverrideFilterConfig(encodedConfig: Any__Output): HttpFilterConfig | null;
|
||||
httpFilterConstructor: HttpFilterFactoryConstructor<Filter>;
|
||||
}
|
||||
|
||||
const FILTER_REGISTRY = new Map<string, HttpFilterRegistryEntry>();
|
||||
|
||||
export function registerHttpFilter(typeName: string, entry: HttpFilterRegistryEntry) {
|
||||
FILTER_REGISTRY.set(typeName, entry);
|
||||
}
|
||||
|
||||
const toObjectOptions = {
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
}
|
||||
|
||||
function parseAnyMessage<MessageType>(message: Any__Output): MessageType | null {
|
||||
const messageType = resourceRoot.lookup(message.type_url);
|
||||
if (messageType) {
|
||||
const decodedMessage = (messageType as any).decode(message.value);
|
||||
return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function getTopLevelFilterUrl(encodedConfig: Any__Output): string {
|
||||
let typeUrl: string;
|
||||
if (encodedConfig.type_url === TYPED_STRUCT_URL) {
|
||||
const typedStruct = parseAnyMessage<TypedStruct__Output>(encodedConfig)
|
||||
if (typedStruct) {
|
||||
return typedStruct.type_url;
|
||||
} else {
|
||||
throw new Error('Failed to parse TypedStruct');
|
||||
}
|
||||
} else {
|
||||
return encodedConfig.type_url;
|
||||
}
|
||||
}
|
||||
|
||||
export function validateTopLevelFilter(httpFilter: HttpFilter__Output): boolean {
|
||||
if (!httpFilter.typed_config) {
|
||||
return false;
|
||||
}
|
||||
const encodedConfig = httpFilter.typed_config;
|
||||
let typeUrl: string;
|
||||
try {
|
||||
typeUrl = getTopLevelFilterUrl(encodedConfig);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
const registryEntry = FILTER_REGISTRY.get(typeUrl);
|
||||
if (registryEntry) {
|
||||
const parsedConfig = registryEntry.parseTopLevelFilterConfig(encodedConfig);
|
||||
return parsedConfig !== null;
|
||||
} else {
|
||||
if (httpFilter.is_optional) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validateOverrideFilter(encodedConfig: Any__Output): boolean {
|
||||
let typeUrl: string;
|
||||
let realConfig: Any__Output;
|
||||
let isOptional = false;
|
||||
if (encodedConfig.type_url === FILTER_CONFIG_URL) {
|
||||
const filterConfig = parseAnyMessage<FilterConfig__Output>(encodedConfig);
|
||||
if (filterConfig) {
|
||||
isOptional = filterConfig.is_optional;
|
||||
if (filterConfig.config) {
|
||||
realConfig = filterConfig.config;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
realConfig = encodedConfig;
|
||||
}
|
||||
if (realConfig.type_url === TYPED_STRUCT_URL) {
|
||||
const typedStruct = parseAnyMessage<TypedStruct__Output>(encodedConfig);
|
||||
if (typedStruct) {
|
||||
typeUrl = typedStruct.type_url;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
typeUrl = realConfig.type_url;
|
||||
}
|
||||
const registryEntry = FILTER_REGISTRY.get(typeUrl);
|
||||
if (registryEntry) {
|
||||
const parsedConfig = registryEntry.parseOverrideFilterConfig(encodedConfig);
|
||||
return parsedConfig !== null;
|
||||
} else {
|
||||
if (isOptional) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function parseTopLevelFilterConfig(encodedConfig: Any__Output) {
|
||||
let typeUrl: string;
|
||||
try {
|
||||
typeUrl = getTopLevelFilterUrl(encodedConfig);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
const registryEntry = FILTER_REGISTRY.get(typeUrl);
|
||||
if (registryEntry) {
|
||||
return registryEntry.parseTopLevelFilterConfig(encodedConfig);
|
||||
} else {
|
||||
// Filter type URL not found in registry
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function parseOverrideFilterConfig(encodedConfig: Any__Output) {
|
||||
let typeUrl: string;
|
||||
let realConfig: Any__Output;
|
||||
if (encodedConfig.type_url === FILTER_CONFIG_URL) {
|
||||
const filterConfig = parseAnyMessage<FilterConfig__Output>(encodedConfig);
|
||||
if (filterConfig) {
|
||||
if (filterConfig.config) {
|
||||
realConfig = filterConfig.config;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
realConfig = encodedConfig;
|
||||
}
|
||||
if (realConfig.type_url === TYPED_STRUCT_URL) {
|
||||
const typedStruct = parseAnyMessage<TypedStruct__Output>(encodedConfig);
|
||||
if (typedStruct) {
|
||||
typeUrl = typedStruct.type_url;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
typeUrl = realConfig.type_url;
|
||||
}
|
||||
const registryEntry = FILTER_REGISTRY.get(typeUrl);
|
||||
if (registryEntry) {
|
||||
return registryEntry.parseOverrideFilterConfig(encodedConfig);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function createHttpFilter(config: HttpFilterConfig, overrideConfig?: HttpFilterConfig): FilterFactory<Filter> | null {
|
||||
const registryEntry = FILTER_REGISTRY.get(config.typeUrl);
|
||||
if (registryEntry) {
|
||||
return new registryEntry.httpFilterConstructor(config, overrideConfig);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2021 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// This is a non-public, unstable API, but it's very convenient
|
||||
import { loadProtosWithOptionsSync } from '@grpc/proto-loader/build/src/util';
|
||||
import { Any__Output } from './generated/google/protobuf/Any';
|
||||
|
||||
function parseAnyMessage(encodedMessage: Any__Output) {
|
||||
|
||||
}
|
|
@ -42,6 +42,12 @@ import { RouteAction, SingleClusterRouteAction, WeightedCluster, WeightedCluster
|
|||
import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from './resources';
|
||||
import Duration = experimental.Duration;
|
||||
import { Duration__Output } from './generated/google/protobuf/Duration';
|
||||
import { createHttpFilter, HttpFilterConfig, parseOverrideFilterConfig, parseTopLevelFilterConfig } from './http-filter';
|
||||
import { EXPERIMENTAL_FAULT_INJECTION } from './environment';
|
||||
import Filter = experimental.Filter;
|
||||
import FilterFactory = experimental.FilterFactory;
|
||||
import BaseFilter = experimental.BaseFilter;
|
||||
import CallStream = experimental.CallStream;
|
||||
|
||||
const TRACER_NAME = 'xds_resolver';
|
||||
|
||||
|
@ -203,6 +209,25 @@ function protoDurationToDuration(duration: Duration__Output): Duration {
|
|||
}
|
||||
}
|
||||
|
||||
class NoRouterFilter extends BaseFilter implements Filter {
|
||||
constructor(private call: CallStream) {
|
||||
super();
|
||||
}
|
||||
|
||||
sendMetadata(metadata: Promise<Metadata>): Promise<Metadata> {
|
||||
this.call.cancelWithStatus(status.UNAVAILABLE, 'no xDS HTTP router filter configured');
|
||||
return Promise.reject<Metadata>(new Error('no xDS HTTP router filter configured'));
|
||||
}
|
||||
}
|
||||
|
||||
class NoRouterFilterFactory implements FilterFactory<NoRouterFilter> {
|
||||
createFilter(callStream: CallStream): NoRouterFilter {
|
||||
return new NoRouterFilter(callStream);
|
||||
}
|
||||
}
|
||||
|
||||
const ROUTER_FILTER_URL = 'type.googleapis.com/envoy.extensions.filters.http.router.v3.Router';
|
||||
|
||||
class XdsResolver implements Resolver {
|
||||
private hasReportedSuccess = false;
|
||||
|
||||
|
@ -221,6 +246,9 @@ class XdsResolver implements Resolver {
|
|||
|
||||
private latestDefaultTimeout: Duration | undefined = undefined;
|
||||
|
||||
private ldsHttpFilterConfigs: {name: string, config: HttpFilterConfig}[] = [];
|
||||
private hasRouterFilter = false;
|
||||
|
||||
constructor(
|
||||
private target: GrpcUri,
|
||||
private listener: ResolverListener,
|
||||
|
@ -235,6 +263,21 @@ class XdsResolver implements Resolver {
|
|||
} else {
|
||||
this.latestDefaultTimeout = protoDurationToDuration(defaultTimeout);
|
||||
}
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
this.ldsHttpFilterConfigs = [];
|
||||
this.hasRouterFilter = false;
|
||||
for (const filter of httpConnectionManager.http_filters) {
|
||||
if (filter.typed_config?.type_url === ROUTER_FILTER_URL) {
|
||||
this.hasRouterFilter = true;
|
||||
break;
|
||||
}
|
||||
// typed_config must be set here, or validation would have failed
|
||||
const filterConfig = parseTopLevelFilterConfig(filter.typed_config!);
|
||||
if (filterConfig) {
|
||||
this.ldsHttpFilterConfigs.push({name: filter.name, config: filterConfig});
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (httpConnectionManager.route_specifier) {
|
||||
case 'rds': {
|
||||
const routeConfigName = httpConnectionManager.rds!.route_config_name;
|
||||
|
@ -314,6 +357,15 @@ class XdsResolver implements Resolver {
|
|||
this.reportResolutionError('No matching route found');
|
||||
return;
|
||||
}
|
||||
const virtualHostHttpFilterOverrides = new Map<string, HttpFilterConfig>();
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const [name, filter] of Object.entries(virtualHost.typed_per_filter_config ?? {})) {
|
||||
const parsedConfig = parseOverrideFilterConfig(filter);
|
||||
if (parsedConfig) {
|
||||
virtualHostHttpFilterOverrides.set(name, parsedConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
trace('Received virtual host config ' + JSON.stringify(virtualHost, undefined, 2));
|
||||
const allConfigClusters = new Set<string>();
|
||||
const matchList: {matcher: Matcher, action: RouteAction}[] = [];
|
||||
|
@ -334,20 +386,89 @@ class XdsResolver implements Resolver {
|
|||
if (timeout?.seconds === 0 && timeout.nanos === 0) {
|
||||
timeout = undefined;
|
||||
}
|
||||
const routeHttpFilterOverrides = new Map<string, HttpFilterConfig>();
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const [name, filter] of Object.entries(route.typed_per_filter_config ?? {})) {
|
||||
const parsedConfig = parseOverrideFilterConfig(filter);
|
||||
if (parsedConfig) {
|
||||
routeHttpFilterOverrides.set(name, parsedConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (route.route!.cluster_specifier) {
|
||||
case 'cluster_header':
|
||||
continue;
|
||||
case 'cluster':{
|
||||
const cluster = route.route!.cluster!;
|
||||
allConfigClusters.add(cluster);
|
||||
routeAction = new SingleClusterRouteAction(cluster, timeout);
|
||||
const extraFilterFactories: FilterFactory<Filter>[] = [];
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const filterConfig of this.ldsHttpFilterConfigs) {
|
||||
if (routeHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else {
|
||||
const filter = createHttpFilter(filterConfig.config);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.hasRouterFilter) {
|
||||
extraFilterFactories.push(new NoRouterFilterFactory());
|
||||
}
|
||||
}
|
||||
routeAction = new SingleClusterRouteAction(cluster, timeout, extraFilterFactories);
|
||||
break;
|
||||
}
|
||||
case 'weighted_clusters': {
|
||||
const weightedClusters: WeightedCluster[] = [];
|
||||
for (const clusterWeight of route.route!.weighted_clusters!.clusters) {
|
||||
allConfigClusters.add(clusterWeight.name);
|
||||
weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0});
|
||||
const extraFilterFactories: FilterFactory<Filter>[] = [];
|
||||
const clusterHttpFilterOverrides = new Map<string, HttpFilterConfig>();
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const [name, filter] of Object.entries(clusterWeight.typed_per_filter_config ?? {})) {
|
||||
const parsedConfig = parseOverrideFilterConfig(filter);
|
||||
if (parsedConfig) {
|
||||
clusterHttpFilterOverrides.set(name, parsedConfig);
|
||||
}
|
||||
}
|
||||
for (const filterConfig of this.ldsHttpFilterConfigs) {
|
||||
if (clusterHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, clusterHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else if (routeHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, routeHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else if (virtualHostHttpFilterOverrides.has(filterConfig.name)) {
|
||||
const filter = createHttpFilter(filterConfig.config, virtualHostHttpFilterOverrides.get(filterConfig.name)!);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
} else {
|
||||
const filter = createHttpFilter(filterConfig.config);
|
||||
if (filter) {
|
||||
extraFilterFactories.push(filter);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!this.hasRouterFilter) {
|
||||
extraFilterFactories.push(new NoRouterFilterFactory());
|
||||
}
|
||||
}
|
||||
weightedClusters.push({name: clusterWeight.name, weight: clusterWeight.weight?.value ?? 0, extraFilterFactories: extraFilterFactories});
|
||||
}
|
||||
routeAction = new WeightedClusterRouteAction(weightedClusters, route.route!.weighted_clusters!.total_weight?.value ?? 100, timeout);
|
||||
}
|
||||
|
@ -376,16 +497,17 @@ class XdsResolver implements Resolver {
|
|||
const configSelector: ConfigSelector = (methodName, metadata) => {
|
||||
for (const {matcher, action} of matchList) {
|
||||
if (matcher.apply(methodName, metadata)) {
|
||||
const clusterName = action.getCluster();
|
||||
this.refCluster(clusterName);
|
||||
const clusterResult = action.getCluster();
|
||||
this.refCluster(clusterResult.name);
|
||||
const onCommitted = () => {
|
||||
this.unrefCluster(clusterName);
|
||||
this.unrefCluster(clusterResult.name);
|
||||
}
|
||||
return {
|
||||
methodConfig: {name: [], timeout: action.getTimeout()},
|
||||
onCommitted: onCommitted,
|
||||
pickInformation: {cluster: clusterName},
|
||||
status: status.OK
|
||||
pickInformation: {cluster: clusterResult.name},
|
||||
status: status.OK,
|
||||
extraFilterFactories: clusterResult.extraFilterFactories
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -393,7 +515,8 @@ class XdsResolver implements Resolver {
|
|||
methodConfig: {name: []},
|
||||
// cluster won't be used here, but it's set because of some TypeScript weirdness
|
||||
pickInformation: {cluster: ''},
|
||||
status: status.UNAVAILABLE
|
||||
status: status.UNAVAILABLE,
|
||||
extraFilterFactories: []
|
||||
};
|
||||
};
|
||||
trace('Created ConfigSelector with configuration:');
|
||||
|
|
|
@ -16,10 +16,17 @@
|
|||
|
||||
import { experimental } from '@grpc/grpc-js';
|
||||
import Duration = experimental.Duration;
|
||||
import Filter = experimental.Filter;
|
||||
import FilterFactory = experimental.FilterFactory;
|
||||
|
||||
export interface ClusterResult {
|
||||
name: string;
|
||||
extraFilterFactories: FilterFactory<Filter>[];
|
||||
}
|
||||
|
||||
export interface RouteAction {
|
||||
toString(): string;
|
||||
getCluster(): string;
|
||||
getCluster(): ClusterResult;
|
||||
getTimeout(): Duration | undefined;
|
||||
}
|
||||
|
||||
|
@ -33,10 +40,13 @@ function durationToLogString(duration: Duration) {
|
|||
}
|
||||
|
||||
export class SingleClusterRouteAction implements RouteAction {
|
||||
constructor(private cluster: string, private timeout: Duration | undefined) {}
|
||||
constructor(private cluster: string, private timeout: Duration | undefined, private extraFilterFactories: FilterFactory<Filter>[]) {}
|
||||
|
||||
getCluster() {
|
||||
return this.cluster;
|
||||
return {
|
||||
name: this.cluster,
|
||||
extraFilterFactories: this.extraFilterFactories
|
||||
};
|
||||
}
|
||||
|
||||
toString() {
|
||||
|
@ -55,11 +65,13 @@ export class SingleClusterRouteAction implements RouteAction {
|
|||
export interface WeightedCluster {
|
||||
name: string;
|
||||
weight: number;
|
||||
extraFilterFactories: FilterFactory<Filter>[];
|
||||
}
|
||||
|
||||
interface ClusterChoice {
|
||||
name: string;
|
||||
numerator: number;
|
||||
extraFilterFactories: FilterFactory<Filter>[];
|
||||
}
|
||||
|
||||
export class WeightedClusterRouteAction implements RouteAction {
|
||||
|
@ -72,7 +84,7 @@ export class WeightedClusterRouteAction implements RouteAction {
|
|||
let lastNumerator = 0;
|
||||
for (const clusterWeight of clusters) {
|
||||
lastNumerator += clusterWeight.weight;
|
||||
this.clusterChoices.push({name: clusterWeight.name, numerator: lastNumerator});
|
||||
this.clusterChoices.push({name: clusterWeight.name, numerator: lastNumerator, extraFilterFactories: clusterWeight.extraFilterFactories});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -80,11 +92,14 @@ export class WeightedClusterRouteAction implements RouteAction {
|
|||
const randomNumber = Math.random() * this.totalWeight;
|
||||
for (const choice of this.clusterChoices) {
|
||||
if (randomNumber < choice.numerator) {
|
||||
return choice.name;
|
||||
return {
|
||||
name: choice.name,
|
||||
extraFilterFactories: choice.extraFilterFactories
|
||||
};
|
||||
}
|
||||
}
|
||||
// This should be prevented by the validation rules
|
||||
return '';
|
||||
return {name: '', extraFilterFactories: []};
|
||||
}
|
||||
|
||||
toString() {
|
||||
|
|
|
@ -22,6 +22,8 @@ import { RdsState } from "./rds-state";
|
|||
import { Watcher, XdsStreamState } from "./xds-stream-state";
|
||||
import { HttpConnectionManager__Output } from '../generated/envoy/extensions/filters/network/http_connection_manager/v3/HttpConnectionManager';
|
||||
import { decodeSingleResource, HTTP_CONNECTION_MANGER_TYPE_URL_V2, HTTP_CONNECTION_MANGER_TYPE_URL_V3 } from '../resources';
|
||||
import { validateTopLevelFilter } from '../http-filter';
|
||||
import { EXPERIMENTAL_FAULT_INJECTION } from '../environment';
|
||||
|
||||
const TRACER_NAME = 'xds_client';
|
||||
|
||||
|
@ -100,6 +102,13 @@ export class LdsState implements XdsStreamState<Listener__Output> {
|
|||
return false;
|
||||
}
|
||||
const httpConnectionManager = decodeSingleResource(HTTP_CONNECTION_MANGER_TYPE_URL_V3, message.api_listener!.api_listener.value);
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const httpFilter of httpConnectionManager.http_filters) {
|
||||
if (!validateTopLevelFilter(httpFilter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (httpConnectionManager.route_specifier) {
|
||||
case 'rds':
|
||||
return !!httpConnectionManager.rds?.config_source?.ads;
|
||||
|
|
|
@ -16,7 +16,9 @@
|
|||
*/
|
||||
|
||||
import { experimental, logVerbosity, StatusObject } from "@grpc/grpc-js";
|
||||
import { EXPERIMENTAL_FAULT_INJECTION } from "../environment";
|
||||
import { RouteConfiguration__Output } from "../generated/envoy/config/route/v3/RouteConfiguration";
|
||||
import { validateOverrideFilter } from "../http-filter";
|
||||
import { CdsLoadBalancingConfig } from "../load-balancer-cds";
|
||||
import { Watcher, XdsStreamState } from "./xds-stream-state";
|
||||
import ServiceConfig = experimental.ServiceConfig;
|
||||
|
@ -112,6 +114,13 @@ export class RdsState implements XdsStreamState<RouteConfiguration__Output> {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const filterConfig of Object.values(virtualHost.typed_per_filter_config ?? {})) {
|
||||
if (!validateOverrideFilter(filterConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const route of virtualHost.routes) {
|
||||
const match = route.match;
|
||||
if (!match) {
|
||||
|
@ -131,6 +140,13 @@ export class RdsState implements XdsStreamState<RouteConfiguration__Output> {
|
|||
if ((route.route === undefined) || SUPPORTED_CLUSTER_SPECIFIERS.indexOf(route.route.cluster_specifier) < 0) {
|
||||
return false;
|
||||
}
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const filterConfig of Object.values(route.typed_per_filter_config ?? {})) {
|
||||
if (!validateOverrideFilter(filterConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (route.route!.cluster_specifier === 'weighted_clusters') {
|
||||
let weightSum = 0;
|
||||
for (const clusterWeight of route.route.weighted_clusters!.clusters) {
|
||||
|
@ -139,6 +155,15 @@ export class RdsState implements XdsStreamState<RouteConfiguration__Output> {
|
|||
if (weightSum !== route.route.weighted_clusters!.total_weight?.value ?? 100) {
|
||||
return false;
|
||||
}
|
||||
if (EXPERIMENTAL_FAULT_INJECTION) {
|
||||
for (const weightedCluster of route.route!.weighted_clusters!.clusters) {
|
||||
for (const filterConfig of Object.values(weightedCluster.typed_per_filter_config ?? {})) {
|
||||
if (!validateOverrideFilter(filterConfig)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -721,6 +721,10 @@ export class Http2CallStream implements Call {
|
|||
this.configDeadline = configDeadline;
|
||||
}
|
||||
|
||||
addFilterFactories(extraFilterFactories: FilterFactory<Filter>[]) {
|
||||
this.filterStack.push(extraFilterFactories.map(filterFactory => filterFactory.createFilter(this)));
|
||||
}
|
||||
|
||||
startRead() {
|
||||
/* If the stream has ended with an error, we should not emit any more
|
||||
* messages and we should communicate that the stream has ended */
|
||||
|
|
|
@ -536,6 +536,7 @@ export class ChannelImplementation implements Channel {
|
|||
// Refreshing the filters makes the deadline filter pick up the new deadline
|
||||
stream.filterStack.refresh();
|
||||
}
|
||||
stream.addFilterFactories(callConfig.extraFilterFactories);
|
||||
this.tryPick(stream, metadata, callConfig);
|
||||
} else {
|
||||
stream.cancelWithStatus(callConfig.status, "Failed to route call to method " + stream.getMethod());
|
||||
|
|
|
@ -24,12 +24,14 @@ import { GrpcUri, uriToString } from './uri-parser';
|
|||
import { ChannelOptions } from './channel-options';
|
||||
import { Metadata } from './metadata';
|
||||
import { Status } from './constants';
|
||||
import { Filter, FilterFactory } from './filter';
|
||||
|
||||
export interface CallConfig {
|
||||
methodConfig: MethodConfig;
|
||||
onCommitted?: () => void;
|
||||
pickInformation: {[key: string]: string};
|
||||
status: Status;
|
||||
extraFilterFactories: FilterFactory<Filter>[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -58,7 +58,8 @@ function getDefaultConfigSelector(serviceConfig: ServiceConfig | null): ConfigSe
|
|||
return {
|
||||
methodConfig: methodConfig,
|
||||
pickInformation: {},
|
||||
status: Status.OK
|
||||
status: Status.OK,
|
||||
extraFilterFactories: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -67,7 +68,8 @@ function getDefaultConfigSelector(serviceConfig: ServiceConfig | null): ConfigSe
|
|||
return {
|
||||
methodConfig: {name: []},
|
||||
pickInformation: {},
|
||||
status: Status.OK
|
||||
status: Status.OK,
|
||||
extraFilterFactories: []
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue