mirror of https://github.com/grpc/grpc-node.git
grpc-js-core: use Map for metadata store
In more recent versions of Node, Maps are more performant than POJOs when used as maps. Switching to Maps also eliminates an expensive delete operation, as well as uses of hasOwnProperty().
This commit is contained in:
parent
8df65a91a2
commit
6a19cf5205
|
@ -4,19 +4,21 @@ const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/;
|
||||||
const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/;
|
const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/;
|
||||||
|
|
||||||
export type MetadataValue = string|Buffer;
|
export type MetadataValue = string|Buffer;
|
||||||
|
export type MetadataObject = Map<string, MetadataValue[]>;
|
||||||
export interface MetadataObject { [key: string]: MetadataValue[]; }
|
|
||||||
|
|
||||||
function cloneMetadataObject(repr: MetadataObject): MetadataObject {
|
function cloneMetadataObject(repr: MetadataObject): MetadataObject {
|
||||||
const result: MetadataObject = {};
|
const result: MetadataObject = new Map<string, MetadataValue[]>();
|
||||||
forOwn(repr, (value, key) => {
|
|
||||||
result[key] = value.map(v => {
|
repr.forEach((value, key) => {
|
||||||
|
const clonedValue: MetadataValue[] = value.map(v => {
|
||||||
if (v instanceof Buffer) {
|
if (v instanceof Buffer) {
|
||||||
return Buffer.from(v);
|
return Buffer.from(v);
|
||||||
} else {
|
} else {
|
||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
result.set(key, clonedValue);
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -64,7 +66,7 @@ function validate(key: string, value?: MetadataValue): void {
|
||||||
* A class for storing metadata. Keys are normalized to lowercase ASCII.
|
* A class for storing metadata. Keys are normalized to lowercase ASCII.
|
||||||
*/
|
*/
|
||||||
export class Metadata {
|
export class Metadata {
|
||||||
protected internalRepr: MetadataObject = {};
|
protected internalRepr: MetadataObject = new Map<string, MetadataValue[]>();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the given value for the given key by replacing any other values
|
* Sets the given value for the given key by replacing any other values
|
||||||
|
@ -76,7 +78,7 @@ export class Metadata {
|
||||||
set(key: string, value: MetadataValue): void {
|
set(key: string, value: MetadataValue): void {
|
||||||
key = normalizeKey(key);
|
key = normalizeKey(key);
|
||||||
validate(key, value);
|
validate(key, value);
|
||||||
this.internalRepr[key] = [value];
|
this.internalRepr.set(key, [value]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,10 +91,13 @@ export class Metadata {
|
||||||
add(key: string, value: MetadataValue): void {
|
add(key: string, value: MetadataValue): void {
|
||||||
key = normalizeKey(key);
|
key = normalizeKey(key);
|
||||||
validate(key, value);
|
validate(key, value);
|
||||||
if (!this.internalRepr[key]) {
|
|
||||||
this.internalRepr[key] = [value];
|
const existingValue: MetadataValue[]|undefined = this.internalRepr.get(key);
|
||||||
|
|
||||||
|
if (existingValue === undefined) {
|
||||||
|
this.internalRepr.set(key, [value]);
|
||||||
} else {
|
} else {
|
||||||
this.internalRepr[key].push(value);
|
existingValue.push(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,9 +108,7 @@ export class Metadata {
|
||||||
remove(key: string): void {
|
remove(key: string): void {
|
||||||
key = normalizeKey(key);
|
key = normalizeKey(key);
|
||||||
validate(key);
|
validate(key);
|
||||||
if (Object.prototype.hasOwnProperty.call(this.internalRepr, key)) {
|
this.internalRepr.delete(key);
|
||||||
delete this.internalRepr[key];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -116,11 +119,7 @@ export class Metadata {
|
||||||
get(key: string): MetadataValue[] {
|
get(key: string): MetadataValue[] {
|
||||||
key = normalizeKey(key);
|
key = normalizeKey(key);
|
||||||
validate(key);
|
validate(key);
|
||||||
if (Object.prototype.hasOwnProperty.call(this.internalRepr, key)) {
|
return this.internalRepr.get(key) || [];
|
||||||
return this.internalRepr[key];
|
|
||||||
} else {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -130,7 +129,8 @@ export class Metadata {
|
||||||
*/
|
*/
|
||||||
getMap(): {[key: string]: MetadataValue} {
|
getMap(): {[key: string]: MetadataValue} {
|
||||||
const result: {[key: string]: MetadataValue} = {};
|
const result: {[key: string]: MetadataValue} = {};
|
||||||
forOwn(this.internalRepr, (values, key) => {
|
|
||||||
|
this.internalRepr.forEach((values, key) => {
|
||||||
if (values.length > 0) {
|
if (values.length > 0) {
|
||||||
const v = values[0];
|
const v = values[0];
|
||||||
result[key] = v instanceof Buffer ? v.slice() : v;
|
result[key] = v instanceof Buffer ? v.slice() : v;
|
||||||
|
@ -157,8 +157,11 @@ export class Metadata {
|
||||||
* @param other A Metadata object.
|
* @param other A Metadata object.
|
||||||
*/
|
*/
|
||||||
merge(other: Metadata): void {
|
merge(other: Metadata): void {
|
||||||
forOwn(other.internalRepr, (values, key) => {
|
other.internalRepr.forEach((values, key) => {
|
||||||
this.internalRepr[key] = (this.internalRepr[key] || []).concat(values);
|
const mergedValue: MetadataValue[] =
|
||||||
|
(this.internalRepr.get(key) || []).concat(values);
|
||||||
|
|
||||||
|
this.internalRepr.set(key, mergedValue);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,7 +171,7 @@ export class Metadata {
|
||||||
toHttp2Headers(): http2.OutgoingHttpHeaders {
|
toHttp2Headers(): http2.OutgoingHttpHeaders {
|
||||||
// NOTE: Node <8.9 formats http2 headers incorrectly.
|
// NOTE: Node <8.9 formats http2 headers incorrectly.
|
||||||
const result: http2.OutgoingHttpHeaders = {};
|
const result: http2.OutgoingHttpHeaders = {};
|
||||||
forOwn(this.internalRepr, (values, key) => {
|
this.internalRepr.forEach((values, key) => {
|
||||||
// We assume that the user's interaction with this object is limited to
|
// We assume that the user's interaction with this object is limited to
|
||||||
// through its public API (i.e. keys and values are already validated).
|
// through its public API (i.e. keys and values are already validated).
|
||||||
result[key] = values.map((value) => {
|
result[key] = values.map((value) => {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import * as http2 from 'http2';
|
import * as http2 from 'http2';
|
||||||
import {range} from 'lodash';
|
import {range} from 'lodash';
|
||||||
import {Metadata} from '../src/metadata';
|
import {Metadata, MetadataObject, MetadataValue} from '../src/metadata';
|
||||||
|
|
||||||
class TestMetadata extends Metadata {
|
class TestMetadata extends Metadata {
|
||||||
getInternalRepresentation() {
|
getInternalRepresentation() {
|
||||||
|
@ -277,21 +277,24 @@ describe('Metadata', () => {
|
||||||
};
|
};
|
||||||
const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers);
|
const metadataFromHeaders = TestMetadata.fromHttp2Headers(headers);
|
||||||
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
||||||
assert.deepEqual(internalRepr, {
|
const expected: MetadataObject = new Map<string, MetadataValue[]>([
|
||||||
key1: ['value1'],
|
['key1', ['value1']], ['key2', ['value2']],
|
||||||
key2: ['value2'],
|
['key3', ['value3a', 'value3b']],
|
||||||
key3: ['value3a', 'value3b'],
|
[
|
||||||
'key-bin': [
|
'key-bin',
|
||||||
Buffer.from(range(0, 16)), Buffer.from(range(16, 32)),
|
[
|
||||||
Buffer.from(range(0, 32))
|
Buffer.from(range(0, 16)), Buffer.from(range(16, 32)),
|
||||||
|
Buffer.from(range(0, 32))
|
||||||
|
]
|
||||||
]
|
]
|
||||||
});
|
]);
|
||||||
|
assert.deepEqual(internalRepr, expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('creates an empty Metadata object from empty headers', () => {
|
it('creates an empty Metadata object from empty headers', () => {
|
||||||
const metadataFromHeaders = TestMetadata.fromHttp2Headers({});
|
const metadataFromHeaders = TestMetadata.fromHttp2Headers({});
|
||||||
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
const internalRepr = metadataFromHeaders.getInternalRepresentation();
|
||||||
assert.deepEqual(internalRepr, {});
|
assert.deepEqual(internalRepr, new Map<string, MetadataValue[]>());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue