etquia
Version:
Dummy package for the grpc-node repository
246 lines (227 loc) • 8.43 kB
text/typescript
/*
* 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, logVerbosity } from '@grpc/grpc-js';
import { Any__Output } from './generated/google/protobuf/Any';
import Filter = experimental.Filter;
import FilterFactory = experimental.FilterFactory;
import { TypedStruct__Output as TypedStruct__Output } from './generated/xds/type/v3/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 TRACER_NAME = 'http_filter';
function trace(text: string): void {
experimental.trace(logVerbosity.DEBUG, TRACER_NAME, text);
}
const TYPED_STRUCT_UDPA_URL = 'type.googleapis.com/udpa.type.v1.TypedStruct';
const TYPED_STRUCT_UDPA_NAME = 'udpa.type.v1.TypedStruct';
const TYPED_STRUCT_XDS_URL = 'type.googleapis.com/xds.type.v3.TypedStruct';
const TYPED_STRUCT_XDS_NAME = 'xds.type.v3.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',
'xds/type/v3/typed_struct.proto',
'envoy/config/route/v3/route_components.proto'], {
keepCase: true,
includeDirs: [
// Paths are relative to src/build
__dirname + '/../../deps/xds/',
__dirname + '/../../deps/envoy-api/',
__dirname + '/../../deps/protoc-gen-validate/'
],
}
);
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) {
trace('Registered filter with type URL ' + typeName);
FILTER_REGISTRY.set(typeName, entry);
}
const toObjectOptions = {
longs: String,
enums: String,
defaults: true,
oneofs: true
}
function parseAnyMessage<MessageType>(message: Any__Output): MessageType | null {
const typeName = message.type_url.substring(message.type_url.lastIndexOf('/') + 1);
const messageType = resourceRoot.lookup(typeName);
if (messageType) {
const decodedMessage = (messageType as any).decode(message.value);
return decodedMessage.$type.toObject(decodedMessage, toObjectOptions) as MessageType;
} else {
return null;
}
}
export function getTopLevelFilterUrl(encodedConfig: Any__Output): string {
let typeUrl: string;
if (encodedConfig.type_url === TYPED_STRUCT_UDPA_URL || encodedConfig.type_url === TYPED_STRUCT_XDS_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) {
trace(httpFilter.name + ' validation failed: typed_config unset');
return false;
}
const encodedConfig = httpFilter.typed_config;
let typeUrl: string;
try {
typeUrl = getTopLevelFilterUrl(encodedConfig);
} catch (e) {
trace(httpFilter.name + ' validation failed with error ' + e.message);
return false;
}
const registryEntry = FILTER_REGISTRY.get(typeUrl);
if (registryEntry) {
const parsedConfig = registryEntry.parseTopLevelFilterConfig(encodedConfig);
if (parsedConfig === null) {
trace(httpFilter.name + ' validation failed: config parsing failed');
}
return parsedConfig !== null;
} else {
if (httpFilter.is_optional) {
return true;
} else {
trace(httpFilter.name + ' validation failed: filter is not optional and registry does not contain type URL ' + typeUrl);
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 {
trace('Override filter validation failed: FilterConfig config field is empty');
return false;
}
} else {
trace('Override filter validation failed: failed to parse FilterConfig message');
return false;
}
} else {
realConfig = encodedConfig;
}
if (realConfig.type_url === TYPED_STRUCT_UDPA_URL || realConfig.type_url === TYPED_STRUCT_XDS_URL) {
const typedStruct = parseAnyMessage<TypedStruct__Output>(encodedConfig);
if (typedStruct) {
typeUrl = typedStruct.type_url;
} else {
trace('Override filter validation failed: failed to parse TypedStruct message');
return false;
}
} else {
typeUrl = realConfig.type_url;
}
const registryEntry = FILTER_REGISTRY.get(typeUrl);
if (registryEntry) {
const parsedConfig = registryEntry.parseOverrideFilterConfig(encodedConfig);
if (parsedConfig === null) {
trace('Override filter validation failed: config parsing failed. Type URL: ' + typeUrl);
}
return parsedConfig !== null;
} else {
if (isOptional) {
return true;
} else {
trace('Override filter validation failed: filter is not optional and registry does not contain type URL ' + typeUrl);
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_UDPA_URL || realConfig.type_url === TYPED_STRUCT_XDS_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;
}
}