mirror of https://github.com/grpc/grpc-node.git
Merge branch 'master' into grpc-js_client_interceptors
This commit is contained in:
commit
738dbf8f02
|
|
@ -3,7 +3,7 @@
|
||||||
Feature | `grpc` | `@grpc/grpc-js`
|
Feature | `grpc` | `@grpc/grpc-js`
|
||||||
--------|--------|----------
|
--------|--------|----------
|
||||||
Client | :heavy_check_mark: | :heavy_check_mark:
|
Client | :heavy_check_mark: | :heavy_check_mark:
|
||||||
Server | :heavy_check_mark: | :x:
|
Server | :heavy_check_mark: | :heavy_check_mark:
|
||||||
Unary RPCs | :heavy_check_mark: | :heavy_check_mark:
|
Unary RPCs | :heavy_check_mark: | :heavy_check_mark:
|
||||||
Streaming RPCs | :heavy_check_mark: | :heavy_check_mark:
|
Streaming RPCs | :heavy_check_mark: | :heavy_check_mark:
|
||||||
Deadlines | :heavy_check_mark: | :heavy_check_mark:
|
Deadlines | :heavy_check_mark: | :heavy_check_mark:
|
||||||
|
|
@ -17,8 +17,8 @@ Connection Keepalives | :heavy_check_mark: | :heavy_check_mark:
|
||||||
HTTP Connect Support | :heavy_check_mark: | :x:
|
HTTP Connect Support | :heavy_check_mark: | :x:
|
||||||
Retries | :heavy_check_mark: | :x:
|
Retries | :heavy_check_mark: | :x:
|
||||||
Stats/tracing/monitoring | :heavy_check_mark: | :x:
|
Stats/tracing/monitoring | :heavy_check_mark: | :x:
|
||||||
Load Balancing | :heavy_check_mark: | :x:
|
Load Balancing | :heavy_check_mark: | Pick first and round robin
|
||||||
Initial Metadata Options | :heavy_check_mark: | :x:
|
Initial Metadata Options | :heavy_check_mark: | only `waitForReady`
|
||||||
|
|
||||||
Other Properties | `grpc` | `@grpc/grpc-js`
|
Other Properties | `grpc` | `@grpc/grpc-js`
|
||||||
-----------------|--------|----------------
|
-----------------|--------|----------------
|
||||||
|
|
@ -37,5 +37,9 @@ In addition, all channel arguments defined in [this header file](https://github.
|
||||||
- `grpc.keepalive_time_ms`
|
- `grpc.keepalive_time_ms`
|
||||||
- `grpc.keepalive_timeout_ms`
|
- `grpc.keepalive_timeout_ms`
|
||||||
- `grpc.service_config`
|
- `grpc.service_config`
|
||||||
|
- `grpc.max_concurrent_streams`
|
||||||
|
- `grpc.initial_reconnect_backoff_ms`
|
||||||
|
- `grpc.max_reconnect_backoff_ms`
|
||||||
|
- `grpc.use_local_subchannel_pool`
|
||||||
- `channelOverride`
|
- `channelOverride`
|
||||||
- `channelFactoryOverride`
|
- `channelFactoryOverride`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# Compression
|
||||||
|
|
||||||
|
## Client side
|
||||||
|
The preferred method for configuring message compression on a client is to pass `options` when the client object is instantiated.
|
||||||
|
|
||||||
|
These two options control compression behavior:
|
||||||
|
|
||||||
|
**grpc.default_compression_algorithm** (int)
|
||||||
|
|
||||||
|
Default compression algorithm for the channel, applies to sending messages.
|
||||||
|
|
||||||
|
Possible values for this option are:
|
||||||
|
- `0` - No compression
|
||||||
|
- `1` - Compress with DEFLATE algorithm
|
||||||
|
- `2` - Compress with GZIP algorithm
|
||||||
|
- `3` - Stream compression with GZIP algorithm
|
||||||
|
|
||||||
|
**grpc.default_compression_level** (int)
|
||||||
|
|
||||||
|
Default compression level for the channel, applies to receiving messages.
|
||||||
|
|
||||||
|
Possible values for this option are:
|
||||||
|
- `0` - None
|
||||||
|
- `1` - Low level
|
||||||
|
- `2` - Medium level
|
||||||
|
- `3` - High level
|
||||||
|
|
||||||
|
### Code example
|
||||||
|
```javascript
|
||||||
|
client = new ExampleClient("example.com", credentials.createInsecure(), {'grpc.default_compression_algorithm': 2, 'grpc.default_compression_level': 2});
|
||||||
|
```
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "@grpc/grpc-js",
|
"name": "@grpc/grpc-js",
|
||||||
"version": "0.6.11",
|
"version": "0.6.15",
|
||||||
"description": "gRPC Library for Node - pure JS implementation",
|
"description": "gRPC Library for Node - pure JS implementation",
|
||||||
"homepage": "https://grpc.io/",
|
"homepage": "https://grpc.io/",
|
||||||
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
|
"repository": "https://github.com/grpc/grpc-node/tree/master/packages/grpc-js",
|
||||||
|
|
@ -58,7 +58,8 @@
|
||||||
"semver": "^6.2.0"
|
"semver": "^6.2.0"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"build/src/*.{js,d.ts}",
|
"src/*.ts",
|
||||||
|
"build/src/*.{js,d.ts,js.map}",
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,10 @@ import { Metadata } from './metadata';
|
||||||
import { StreamDecoder } from './stream-decoder';
|
import { StreamDecoder } from './stream-decoder';
|
||||||
import { ChannelImplementation } from './channel';
|
import { ChannelImplementation } from './channel';
|
||||||
import { Subchannel } from './subchannel';
|
import { Subchannel } from './subchannel';
|
||||||
|
import * as logging from './logging';
|
||||||
|
import { LogVerbosity } from './constants';
|
||||||
|
|
||||||
|
const TRACER_NAME = 'call_stream';
|
||||||
|
|
||||||
const {
|
const {
|
||||||
HTTP2_HEADER_STATUS,
|
HTTP2_HEADER_STATUS,
|
||||||
|
|
@ -202,7 +206,8 @@ export class Http2CallStream implements Call {
|
||||||
private readonly channel: ChannelImplementation,
|
private readonly channel: ChannelImplementation,
|
||||||
private readonly options: CallStreamOptions,
|
private readonly options: CallStreamOptions,
|
||||||
filterStackFactory: FilterStackFactory,
|
filterStackFactory: FilterStackFactory,
|
||||||
private readonly channelCallCredentials: CallCredentials
|
private readonly channelCallCredentials: CallCredentials,
|
||||||
|
private readonly callNumber: number
|
||||||
) {
|
) {
|
||||||
this.filterStack = filterStackFactory.createFilter(this);
|
this.filterStack = filterStackFactory.createFilter(this);
|
||||||
this.credentials = channelCallCredentials;
|
this.credentials = channelCallCredentials;
|
||||||
|
|
@ -227,6 +232,14 @@ export class Http2CallStream implements Call {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private trace(text: string): void {
|
||||||
|
logging.trace(
|
||||||
|
LogVerbosity.DEBUG,
|
||||||
|
TRACER_NAME,
|
||||||
|
'[' + this.callNumber + '] ' + text
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On first call, emits a 'status' event with the given StatusObject.
|
* On first call, emits a 'status' event with the given StatusObject.
|
||||||
* Subsequent calls are no-ops.
|
* Subsequent calls are no-ops.
|
||||||
|
|
@ -236,6 +249,13 @@ export class Http2CallStream implements Call {
|
||||||
/* If the status is OK and a new status comes in (e.g. from a
|
/* If the status is OK and a new status comes in (e.g. from a
|
||||||
* deserialization failure), that new status takes priority */
|
* deserialization failure), that new status takes priority */
|
||||||
if (this.finalStatus === null || this.finalStatus.code === Status.OK) {
|
if (this.finalStatus === null || this.finalStatus.code === Status.OK) {
|
||||||
|
this.trace(
|
||||||
|
'ended with status: code=' +
|
||||||
|
status.code +
|
||||||
|
' details="' +
|
||||||
|
status.details +
|
||||||
|
'"'
|
||||||
|
);
|
||||||
this.finalStatus = status;
|
this.finalStatus = status;
|
||||||
this.maybeOutputStatus();
|
this.maybeOutputStatus();
|
||||||
}
|
}
|
||||||
|
|
@ -259,6 +279,10 @@ export class Http2CallStream implements Call {
|
||||||
}
|
}
|
||||||
|
|
||||||
private push(message: Buffer): void {
|
private push(message: Buffer): void {
|
||||||
|
this.trace(
|
||||||
|
'pushing to reader message of length ' +
|
||||||
|
(message instanceof Buffer ? message.length : null)
|
||||||
|
);
|
||||||
this.canPush = false;
|
this.canPush = false;
|
||||||
process.nextTick(() => {
|
process.nextTick(() => {
|
||||||
this.listener!.onReceiveMessage(message);
|
this.listener!.onReceiveMessage(message);
|
||||||
|
|
@ -282,6 +306,9 @@ export class Http2CallStream implements Call {
|
||||||
this.http2Stream!.pause();
|
this.http2Stream!.pause();
|
||||||
this.push(message);
|
this.push(message);
|
||||||
} else {
|
} else {
|
||||||
|
this.trace(
|
||||||
|
'unpushedReadMessages.push message of length ' + message.length
|
||||||
|
);
|
||||||
this.unpushedReadMessages.push(message);
|
this.unpushedReadMessages.push(message);
|
||||||
}
|
}
|
||||||
if (this.unfilteredReadMessages.length > 0) {
|
if (this.unfilteredReadMessages.length > 0) {
|
||||||
|
|
@ -299,6 +326,7 @@ export class Http2CallStream implements Call {
|
||||||
this.maybeOutputStatus();
|
this.maybeOutputStatus();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
this.trace('filterReceivedMessage of length ' + framedMessage.length);
|
||||||
this.isReadFilterPending = true;
|
this.isReadFilterPending = true;
|
||||||
this.filterStack
|
this.filterStack
|
||||||
.receiveMessage(Promise.resolve(framedMessage))
|
.receiveMessage(Promise.resolve(framedMessage))
|
||||||
|
|
@ -310,6 +338,12 @@ export class Http2CallStream implements Call {
|
||||||
|
|
||||||
private tryPush(messageBytes: Buffer): void {
|
private tryPush(messageBytes: Buffer): void {
|
||||||
if (this.isReadFilterPending) {
|
if (this.isReadFilterPending) {
|
||||||
|
this.trace(
|
||||||
|
'[' +
|
||||||
|
this.callNumber +
|
||||||
|
'] unfilteredReadMessages.push message of length ' +
|
||||||
|
(messageBytes && messageBytes.length)
|
||||||
|
);
|
||||||
this.unfilteredReadMessages.push(messageBytes);
|
this.unfilteredReadMessages.push(messageBytes);
|
||||||
} else {
|
} else {
|
||||||
this.filterReceivedMessage(messageBytes);
|
this.filterReceivedMessage(messageBytes);
|
||||||
|
|
@ -317,6 +351,7 @@ export class Http2CallStream implements Call {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleTrailers(headers: http2.IncomingHttpHeaders) {
|
private handleTrailers(headers: http2.IncomingHttpHeaders) {
|
||||||
|
this.trace('received HTTP/2 trailing headers frame');
|
||||||
const code: Status = this.mappedStatusCode;
|
const code: Status = this.mappedStatusCode;
|
||||||
const details = '';
|
const details = '';
|
||||||
let metadata: Metadata;
|
let metadata: Metadata;
|
||||||
|
|
@ -350,11 +385,15 @@ export class Http2CallStream implements Call {
|
||||||
if (this.finalStatus !== null) {
|
if (this.finalStatus !== null) {
|
||||||
stream.close(NGHTTP2_CANCEL);
|
stream.close(NGHTTP2_CANCEL);
|
||||||
} else {
|
} else {
|
||||||
|
this.trace(
|
||||||
|
'attachHttp2Stream from subchannel ' + subchannel.getAddress()
|
||||||
|
);
|
||||||
this.http2Stream = stream;
|
this.http2Stream = stream;
|
||||||
this.subchannel = subchannel;
|
this.subchannel = subchannel;
|
||||||
subchannel.addDisconnectListener(this.disconnectListener);
|
subchannel.addDisconnectListener(this.disconnectListener);
|
||||||
subchannel.callRef();
|
subchannel.callRef();
|
||||||
stream.on('response', (headers, flags) => {
|
stream.on('response', (headers, flags) => {
|
||||||
|
this.trace('received HTTP/2 headers frame');
|
||||||
switch (headers[':status']) {
|
switch (headers[':status']) {
|
||||||
// TODO(murgatroid99): handle 100 and 101
|
// TODO(murgatroid99): handle 100 and 101
|
||||||
case 400:
|
case 400:
|
||||||
|
|
@ -408,9 +447,11 @@ export class Http2CallStream implements Call {
|
||||||
});
|
});
|
||||||
stream.on('trailers', this.handleTrailers.bind(this));
|
stream.on('trailers', this.handleTrailers.bind(this));
|
||||||
stream.on('data', (data: Buffer) => {
|
stream.on('data', (data: Buffer) => {
|
||||||
|
this.trace('receive HTTP/2 data frame of length ' + data.length);
|
||||||
const messages = this.decoder.write(data);
|
const messages = this.decoder.write(data);
|
||||||
|
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
|
this.trace('parsed message of length ' + message.length);
|
||||||
this.tryPush(message);
|
this.tryPush(message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
@ -419,6 +460,7 @@ export class Http2CallStream implements Call {
|
||||||
this.maybeOutputStatus();
|
this.maybeOutputStatus();
|
||||||
});
|
});
|
||||||
stream.on('close', () => {
|
stream.on('close', () => {
|
||||||
|
this.trace('HTTP/2 stream closed with code ' + stream.rstCode);
|
||||||
let code: Status;
|
let code: Status;
|
||||||
let details = '';
|
let details = '';
|
||||||
switch (stream.rstCode) {
|
switch (stream.rstCode) {
|
||||||
|
|
@ -477,6 +519,7 @@ export class Http2CallStream implements Call {
|
||||||
}
|
}
|
||||||
|
|
||||||
start(metadata: Metadata, listener: InterceptingListener) {
|
start(metadata: Metadata, listener: InterceptingListener) {
|
||||||
|
this.trace('Sending metadata');
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.channel._startCallStream(this, metadata);
|
this.channel._startCallStream(this, metadata);
|
||||||
}
|
}
|
||||||
|
|
@ -553,11 +596,13 @@ export class Http2CallStream implements Call {
|
||||||
!this.isWriteFilterPending &&
|
!this.isWriteFilterPending &&
|
||||||
this.http2Stream !== null
|
this.http2Stream !== null
|
||||||
) {
|
) {
|
||||||
|
this.trace('calling end() on HTTP/2 stream');
|
||||||
this.http2Stream.end();
|
this.http2Stream.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sendMessageWithContext(context: MessageContext, message: Buffer) {
|
sendMessageWithContext(context: MessageContext, message: Buffer) {
|
||||||
|
this.trace('write() called with message of length ' + message.length);
|
||||||
const writeObj: WriteObject = {
|
const writeObj: WriteObject = {
|
||||||
message,
|
message,
|
||||||
flags: context.flags,
|
flags: context.flags,
|
||||||
|
|
@ -577,6 +622,7 @@ export class Http2CallStream implements Call {
|
||||||
}
|
}
|
||||||
|
|
||||||
halfClose() {
|
halfClose() {
|
||||||
|
this.trace('end() called');
|
||||||
this.writesClosed = true;
|
this.writesClosed = true;
|
||||||
this.maybeCloseWrites();
|
this.maybeCloseWrites();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@
|
||||||
import { ConnectionOptions, createSecureContext, PeerCertificate } from 'tls';
|
import { ConnectionOptions, createSecureContext, PeerCertificate } from 'tls';
|
||||||
|
|
||||||
import { CallCredentials } from './call-credentials';
|
import { CallCredentials } from './call-credentials';
|
||||||
import { Call } from '.';
|
import {CIPHER_SUITES, getDefaultRootsData} from './tls-helpers';
|
||||||
|
|
||||||
// tslint:disable-next-line:no-any
|
// tslint:disable-next-line:no-any
|
||||||
function verifyIsBufferOrNull(obj: any, friendlyName: string): void {
|
function verifyIsBufferOrNull(obj: any, friendlyName: string): void {
|
||||||
|
|
@ -141,7 +141,7 @@ export abstract class ChannelCredentials {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return new SecureChannelCredentialsImpl(
|
return new SecureChannelCredentialsImpl(
|
||||||
rootCerts || null,
|
rootCerts || getDefaultRootsData(),
|
||||||
privateKey || null,
|
privateKey || null,
|
||||||
certChain || null,
|
certChain || null,
|
||||||
verifyOptions || {}
|
verifyOptions || {}
|
||||||
|
|
@ -190,6 +190,7 @@ class SecureChannelCredentialsImpl extends ChannelCredentials {
|
||||||
ca: rootCerts || undefined,
|
ca: rootCerts || undefined,
|
||||||
key: privateKey || undefined,
|
key: privateKey || undefined,
|
||||||
cert: certChain || undefined,
|
cert: certChain || undefined,
|
||||||
|
ciphers: CIPHER_SUITES
|
||||||
});
|
});
|
||||||
this.connectionOptions = { secureContext };
|
this.connectionOptions = { secureContext };
|
||||||
if (verifyOptions && verifyOptions.checkServerIdentity) {
|
if (verifyOptions && verifyOptions.checkServerIdentity) {
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ export interface ChannelOptions {
|
||||||
'grpc.keepalive_time_ms'?: number;
|
'grpc.keepalive_time_ms'?: number;
|
||||||
'grpc.keepalive_timeout_ms'?: number;
|
'grpc.keepalive_timeout_ms'?: number;
|
||||||
'grpc.service_config'?: string;
|
'grpc.service_config'?: string;
|
||||||
|
'grpc.max_concurrent_streams'?: number;
|
||||||
|
'grpc.initial_reconnect_backoff_ms'?: number;
|
||||||
|
'grpc.max_reconnect_backoff_ms'?: number;
|
||||||
|
'grpc.use_local_subchannel_pool'?: number;
|
||||||
[key: string]: string | number | undefined;
|
[key: string]: string | number | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,6 +45,10 @@ export const recognizedOptions = {
|
||||||
'grpc.keepalive_time_ms': true,
|
'grpc.keepalive_time_ms': true,
|
||||||
'grpc.keepalive_timeout_ms': true,
|
'grpc.keepalive_timeout_ms': true,
|
||||||
'grpc.service_config': true,
|
'grpc.service_config': true,
|
||||||
|
'grpc.max_concurrent_streams': true,
|
||||||
|
'grpc.initial_reconnect_backoff_ms': true,
|
||||||
|
'grpc.max_reconnect_backoff_ms': true,
|
||||||
|
'grpc.use_local_subchannel_pool': true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function channelOptionsEqual(
|
export function channelOptionsEqual(
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,17 @@ export enum ConnectivityState {
|
||||||
SHUTDOWN,
|
SHUTDOWN,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let nextCallNumber = 0;
|
||||||
|
|
||||||
|
function getNewCallNumber(): number {
|
||||||
|
const callNumber = nextCallNumber;
|
||||||
|
nextCallNumber += 1;
|
||||||
|
if (nextCallNumber >= Number.MAX_SAFE_INTEGER) {
|
||||||
|
nextCallNumber = 0;
|
||||||
|
}
|
||||||
|
return callNumber;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An interface that represents a communication channel to a server specified
|
* An interface that represents a communication channel to a server specified
|
||||||
* by a given address.
|
* by a given address.
|
||||||
|
|
@ -129,8 +140,9 @@ export class ChannelImplementation implements Channel {
|
||||||
private readonly credentials: ChannelCredentials,
|
private readonly credentials: ChannelCredentials,
|
||||||
private readonly options: ChannelOptions
|
private readonly options: ChannelOptions
|
||||||
) {
|
) {
|
||||||
// TODO(murgatroid99): check channel arg for getting a private pool
|
/* The global boolean parameter to getSubchannelPool has the inverse meaning to what
|
||||||
this.subchannelPool = getSubchannelPool(true);
|
* the grpc.use_local_subchannel_pool channel option means. */
|
||||||
|
this.subchannelPool = getSubchannelPool((options['grpc.use_local_subchannel_pool'] ?? 0) === 0);
|
||||||
const channelControlHelper: ChannelControlHelper = {
|
const channelControlHelper: ChannelControlHelper = {
|
||||||
createSubchannel: (
|
createSubchannel: (
|
||||||
subchannelAddress: string,
|
subchannelAddress: string,
|
||||||
|
|
@ -216,10 +228,17 @@ export class ChannelImplementation implements Channel {
|
||||||
pickResult.subchannel!.getConnectivityState() ===
|
pickResult.subchannel!.getConnectivityState() ===
|
||||||
ConnectivityState.READY
|
ConnectivityState.READY
|
||||||
) {
|
) {
|
||||||
|
try {
|
||||||
pickResult.subchannel!.startCallStream(
|
pickResult.subchannel!.startCallStream(
|
||||||
finalMetadata,
|
finalMetadata,
|
||||||
callStream
|
callStream
|
||||||
);
|
);
|
||||||
|
} catch (error) {
|
||||||
|
callStream.cancelWithStatus(
|
||||||
|
Status.UNAVAILABLE,
|
||||||
|
'Failed to start call on picked subchannel'
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
callStream.cancelWithStatus(
|
callStream.cancelWithStatus(
|
||||||
Status.UNAVAILABLE,
|
Status.UNAVAILABLE,
|
||||||
|
|
@ -304,8 +323,12 @@ export class ChannelImplementation implements Channel {
|
||||||
return this.target;
|
return this.target;
|
||||||
}
|
}
|
||||||
|
|
||||||
getConnectivityState() {
|
getConnectivityState(tryToConnect: boolean) {
|
||||||
return this.connectivityState;
|
const connectivityState = this.connectivityState;
|
||||||
|
if (tryToConnect) {
|
||||||
|
this.resolvingLoadBalancer.exitIdle();
|
||||||
|
}
|
||||||
|
return connectivityState;
|
||||||
}
|
}
|
||||||
|
|
||||||
watchConnectivityState(
|
watchConnectivityState(
|
||||||
|
|
@ -346,6 +369,18 @@ export class ChannelImplementation implements Channel {
|
||||||
if (this.connectivityState === ConnectivityState.SHUTDOWN) {
|
if (this.connectivityState === ConnectivityState.SHUTDOWN) {
|
||||||
throw new Error('Channel has been shut down');
|
throw new Error('Channel has been shut down');
|
||||||
}
|
}
|
||||||
|
const callNumber = getNewCallNumber();
|
||||||
|
trace(
|
||||||
|
LogVerbosity.DEBUG,
|
||||||
|
'channel',
|
||||||
|
this.target +
|
||||||
|
' createCall [' +
|
||||||
|
callNumber +
|
||||||
|
'] method="' +
|
||||||
|
method +
|
||||||
|
'", deadline=' +
|
||||||
|
deadline
|
||||||
|
);
|
||||||
const finalOptions: CallStreamOptions = {
|
const finalOptions: CallStreamOptions = {
|
||||||
deadline:
|
deadline:
|
||||||
deadline === null || deadline === undefined ? Infinity : deadline,
|
deadline === null || deadline === undefined ? Infinity : deadline,
|
||||||
|
|
@ -358,7 +393,8 @@ export class ChannelImplementation implements Channel {
|
||||||
this,
|
this,
|
||||||
finalOptions,
|
finalOptions,
|
||||||
this.filterStackFactory,
|
this.filterStackFactory,
|
||||||
this.credentials._getCallCredentials()
|
this.credentials._getCallCredentials(),
|
||||||
|
callNumber
|
||||||
);
|
);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -85,11 +85,11 @@ export interface OAuth2Client {
|
||||||
callback: (
|
callback: (
|
||||||
err: Error | null,
|
err: Error | null,
|
||||||
headers?: {
|
headers?: {
|
||||||
Authorization: string;
|
[index: string]: string;
|
||||||
}
|
}
|
||||||
) => void
|
) => void
|
||||||
) => void;
|
) => void;
|
||||||
getRequestHeaders: (url?: string) => Promise<{ Authorization: string }>;
|
getRequestHeaders: (url?: string) => Promise<{ [index: string]: string }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**** Client Credentials ****/
|
/**** Client Credentials ****/
|
||||||
|
|
@ -109,7 +109,7 @@ export const credentials = mixin(
|
||||||
(options, callback) => {
|
(options, callback) => {
|
||||||
// google-auth-library pre-v2.0.0 does not have getRequestHeaders
|
// google-auth-library pre-v2.0.0 does not have getRequestHeaders
|
||||||
// but has getRequestMetadata, which is deprecated in v2.0.0
|
// but has getRequestMetadata, which is deprecated in v2.0.0
|
||||||
let getHeaders: Promise<{ Authorization: string }>;
|
let getHeaders: Promise<{ [index: string]: string }>;
|
||||||
if (typeof googleCredentials.getRequestHeaders === 'function') {
|
if (typeof googleCredentials.getRequestHeaders === 'function') {
|
||||||
getHeaders = googleCredentials.getRequestHeaders(
|
getHeaders = googleCredentials.getRequestHeaders(
|
||||||
options.service_url
|
options.service_url
|
||||||
|
|
@ -131,7 +131,9 @@ export const credentials = mixin(
|
||||||
getHeaders.then(
|
getHeaders.then(
|
||||||
headers => {
|
headers => {
|
||||||
const metadata = new Metadata();
|
const metadata = new Metadata();
|
||||||
metadata.add('authorization', headers.Authorization);
|
for (const key of Object.keys(headers)) {
|
||||||
|
metadata.add(key, headers[key]);
|
||||||
|
}
|
||||||
callback(null, metadata);
|
callback(null, metadata);
|
||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import * as http2 from 'http2';
|
import * as http2 from 'http2';
|
||||||
|
import { log } from './logging';
|
||||||
|
import { LogVerbosity } from './constants';
|
||||||
const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/;
|
const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/;
|
||||||
const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/;
|
const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/;
|
||||||
|
|
||||||
|
|
@ -34,6 +36,10 @@ function isBinaryKey(key: string): boolean {
|
||||||
return key.endsWith('-bin');
|
return key.endsWith('-bin');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isCustomMetadata(key: string): boolean {
|
||||||
|
return !key.startsWith('grpc-');
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeKey(key: string): string {
|
function normalizeKey(key: string): string {
|
||||||
return key.toLowerCase();
|
return key.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
@ -258,9 +264,13 @@ export class Metadata {
|
||||||
result.add(key, Buffer.from(value, 'base64'));
|
result.add(key, Buffer.from(value, 'base64'));
|
||||||
});
|
});
|
||||||
} else if (values !== undefined) {
|
} else if (values !== undefined) {
|
||||||
|
if (isCustomMetadata(key)) {
|
||||||
values.split(',').forEach(v => {
|
values.split(',').forEach(v => {
|
||||||
result.add(key, Buffer.from(v.trim(), 'base64'));
|
result.add(key, Buffer.from(v.trim(), 'base64'));
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
result.add(key, Buffer.from(values, 'base64'));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (Array.isArray(values)) {
|
if (Array.isArray(values)) {
|
||||||
|
|
@ -268,12 +278,16 @@ export class Metadata {
|
||||||
result.add(key, value);
|
result.add(key, value);
|
||||||
});
|
});
|
||||||
} else if (values !== undefined) {
|
} else if (values !== undefined) {
|
||||||
|
if (isCustomMetadata(key)) {
|
||||||
values.split(',').forEach(v => result.add(key, v.trim()));
|
values.split(',').forEach(v => result.add(key, v.trim()));
|
||||||
|
} else {
|
||||||
|
result.add(key, values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
error.message = `Failed to add metadata entry ${key}: ${values}. ${error.message}`;
|
const message = `Failed to add metadata entry ${key}: ${values}. ${error.message}. For more information see https://github.com/grpc/grpc-node/issues/1173`;
|
||||||
process.emitWarning(error);
|
log(LogVerbosity.ERROR, message);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return result;
|
return result;
|
||||||
|
|
|
||||||
|
|
@ -115,14 +115,15 @@ const dnsLookupPromise = util.promisify(dns.lookup);
|
||||||
function parseIP(target: string): string[] | null {
|
function parseIP(target: string): string[] | null {
|
||||||
/* These three regular expressions are all mutually exclusive, so we just
|
/* These three regular expressions are all mutually exclusive, so we just
|
||||||
* want the first one that matches the target string, if any do. */
|
* want the first one that matches the target string, if any do. */
|
||||||
|
const ipv4Match = IPV4_REGEX.exec(target);
|
||||||
const match =
|
const match =
|
||||||
IPV4_REGEX.exec(target) ||
|
ipv4Match || IPV6_REGEX.exec(target) || IPV6_BRACKET_REGEX.exec(target);
|
||||||
IPV6_REGEX.exec(target) ||
|
|
||||||
IPV6_BRACKET_REGEX.exec(target);
|
|
||||||
if (match === null) {
|
if (match === null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const addr = match[1];
|
|
||||||
|
// ipv6 addresses should be bracketed
|
||||||
|
const addr = ipv4Match ? match[1] : `[${match[1]}]`;
|
||||||
let port: string;
|
let port: string;
|
||||||
if (match[2]) {
|
if (match[2]) {
|
||||||
port = match[2];
|
port = match[2];
|
||||||
|
|
@ -140,7 +141,11 @@ function mergeArrays<T>(...arrays: T[][]): T[] {
|
||||||
const result: T[] = [];
|
const result: T[] = [];
|
||||||
for (
|
for (
|
||||||
let i = 0;
|
let i = 0;
|
||||||
i < Math.max.apply(null, arrays.map(array => array.length));
|
i <
|
||||||
|
Math.max.apply(
|
||||||
|
null,
|
||||||
|
arrays.map(array => array.length)
|
||||||
|
);
|
||||||
i++
|
i++
|
||||||
) {
|
) {
|
||||||
for (const array of arrays) {
|
for (const array of arrays) {
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ export function createResolver(
|
||||||
* @param target
|
* @param target
|
||||||
*/
|
*/
|
||||||
export function getDefaultAuthority(target: string): string {
|
export function getDefaultAuthority(target: string): string {
|
||||||
for (const prefix of Object.keys(registerDefaultResolver)) {
|
for (const prefix of Object.keys(registeredResolvers)) {
|
||||||
if (target.startsWith(prefix)) {
|
if (target.startsWith(prefix)) {
|
||||||
return registeredResolvers[prefix].getDefaultAuthority(target);
|
return registeredResolvers[prefix].getDefaultAuthority(target);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SecureServerOptions } from 'http2';
|
import { SecureServerOptions } from 'http2';
|
||||||
|
import {CIPHER_SUITES, getDefaultRootsData} from './tls-helpers';
|
||||||
|
|
||||||
export interface KeyCertPair {
|
export interface KeyCertPair {
|
||||||
private_key: Buffer;
|
private_key: Buffer;
|
||||||
|
|
@ -70,10 +71,11 @@ export abstract class ServerCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SecureServerCredentials({
|
return new SecureServerCredentials({
|
||||||
ca: rootCerts || undefined,
|
ca: rootCerts || getDefaultRootsData() || undefined,
|
||||||
cert,
|
cert,
|
||||||
key,
|
key,
|
||||||
requestCert: checkClientCertificate,
|
requestCert: checkClientCertificate,
|
||||||
|
ciphers: CIPHER_SUITES
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ import {
|
||||||
ServerStatusResponse,
|
ServerStatusResponse,
|
||||||
} from './server-call';
|
} from './server-call';
|
||||||
import { ServerCredentials } from './server-credentials';
|
import { ServerCredentials } from './server-credentials';
|
||||||
|
import { ChannelOptions } from './channel-options';
|
||||||
|
|
||||||
function noop(): void {}
|
function noop(): void {}
|
||||||
|
|
||||||
|
|
@ -95,8 +96,11 @@ export class Server {
|
||||||
>();
|
>();
|
||||||
private sessions = new Set<http2.ServerHttp2Session>();
|
private sessions = new Set<http2.ServerHttp2Session>();
|
||||||
private started = false;
|
private started = false;
|
||||||
|
private options: ChannelOptions;
|
||||||
|
|
||||||
constructor(options?: object) {}
|
constructor(options?: ChannelOptions) {
|
||||||
|
this.options = options ?? {};
|
||||||
|
}
|
||||||
|
|
||||||
addProtoService(): void {
|
addProtoService(): void {
|
||||||
throw new Error('Not implemented. Use addService() instead');
|
throw new Error('Not implemented. Use addService() instead');
|
||||||
|
|
@ -197,13 +201,16 @@ export class Server {
|
||||||
|
|
||||||
const url = new URL(`http://${port}`);
|
const url = new URL(`http://${port}`);
|
||||||
const options: ListenOptions = { host: url.hostname, port: +url.port };
|
const options: ListenOptions = { host: url.hostname, port: +url.port };
|
||||||
|
const serverOptions: http2.ServerOptions = {};
|
||||||
|
if ('grpc.max_concurrent_streams' in this.options) {
|
||||||
|
serverOptions.settings = {maxConcurrentStreams: this.options['grpc.max_concurrent_streams']};
|
||||||
|
}
|
||||||
|
|
||||||
if (creds._isSecure()) {
|
if (creds._isSecure()) {
|
||||||
this.http2Server = http2.createSecureServer(
|
const secureServerOptions = Object.assign(serverOptions, creds._getSettings()!);
|
||||||
creds._getSettings() as http2.SecureServerOptions
|
this.http2Server = http2.createSecureServer(secureServerOptions);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.http2Server = http2.createServer();
|
this.http2Server = http2.createServer(serverOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.http2Server.setTimeout(0, noop);
|
this.http2Server.setTimeout(0, noop);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import { Http2CallStream } from './call-stream';
|
||||||
import { ChannelOptions } from './channel-options';
|
import { ChannelOptions } from './channel-options';
|
||||||
import { PeerCertificate, checkServerIdentity } from 'tls';
|
import { PeerCertificate, checkServerIdentity } from 'tls';
|
||||||
import { ConnectivityState } from './channel';
|
import { ConnectivityState } from './channel';
|
||||||
import { BackoffTimeout } from './backoff-timeout';
|
import { BackoffTimeout, BackoffOptions } from './backoff-timeout';
|
||||||
import { getDefaultAuthority } from './resolver';
|
import { getDefaultAuthority } from './resolver';
|
||||||
import * as logging from './logging';
|
import * as logging from './logging';
|
||||||
import { LogVerbosity } from './constants';
|
import { LogVerbosity } from './constants';
|
||||||
|
|
@ -170,6 +170,10 @@ export class Subchannel {
|
||||||
clearTimeout(this.keepaliveIntervalId);
|
clearTimeout(this.keepaliveIntervalId);
|
||||||
this.keepaliveTimeoutId = setTimeout(() => {}, 0);
|
this.keepaliveTimeoutId = setTimeout(() => {}, 0);
|
||||||
clearTimeout(this.keepaliveTimeoutId);
|
clearTimeout(this.keepaliveTimeoutId);
|
||||||
|
const backoffOptions: BackoffOptions = {
|
||||||
|
initialDelay: options['grpc.initial_reconnect_backoff_ms'],
|
||||||
|
maxDelay: options['grpc.max_reconnect_backoff_ms']
|
||||||
|
};
|
||||||
this.backoffTimeout = new BackoffTimeout(() => {
|
this.backoffTimeout = new BackoffTimeout(() => {
|
||||||
if (this.continueConnecting) {
|
if (this.continueConnecting) {
|
||||||
this.transitionToState(
|
this.transitionToState(
|
||||||
|
|
@ -182,7 +186,7 @@ export class Subchannel {
|
||||||
ConnectivityState.IDLE
|
ConnectivityState.IDLE
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}, backoffOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -395,6 +399,13 @@ export class Subchannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
callRef() {
|
callRef() {
|
||||||
|
trace(
|
||||||
|
this.subchannelAddress +
|
||||||
|
' callRefcount ' +
|
||||||
|
this.callRefcount +
|
||||||
|
' -> ' +
|
||||||
|
(this.callRefcount + 1)
|
||||||
|
);
|
||||||
if (this.callRefcount === 0) {
|
if (this.callRefcount === 0) {
|
||||||
if (this.session) {
|
if (this.session) {
|
||||||
this.session.ref();
|
this.session.ref();
|
||||||
|
|
@ -405,6 +416,13 @@ export class Subchannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
callUnref() {
|
callUnref() {
|
||||||
|
trace(
|
||||||
|
this.subchannelAddress +
|
||||||
|
' callRefcount ' +
|
||||||
|
this.callRefcount +
|
||||||
|
' -> ' +
|
||||||
|
(this.callRefcount - 1)
|
||||||
|
);
|
||||||
this.callRefcount -= 1;
|
this.callRefcount -= 1;
|
||||||
if (this.callRefcount === 0) {
|
if (this.callRefcount === 0) {
|
||||||
if (this.session) {
|
if (this.session) {
|
||||||
|
|
@ -416,10 +434,24 @@ export class Subchannel {
|
||||||
}
|
}
|
||||||
|
|
||||||
ref() {
|
ref() {
|
||||||
|
trace(
|
||||||
|
this.subchannelAddress +
|
||||||
|
' callRefcount ' +
|
||||||
|
this.refcount +
|
||||||
|
' -> ' +
|
||||||
|
(this.refcount + 1)
|
||||||
|
);
|
||||||
this.refcount += 1;
|
this.refcount += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
unref() {
|
unref() {
|
||||||
|
trace(
|
||||||
|
this.subchannelAddress +
|
||||||
|
' callRefcount ' +
|
||||||
|
this.refcount +
|
||||||
|
' -> ' +
|
||||||
|
(this.refcount - 1)
|
||||||
|
);
|
||||||
this.refcount -= 1;
|
this.refcount -= 1;
|
||||||
this.checkBothRefcounts();
|
this.checkBothRefcounts();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
export const CIPHER_SUITES: string | undefined = process.env.GRPC_SSL_CIPHER_SUITES;
|
||||||
|
|
||||||
|
const DEFAULT_ROOTS_FILE_PATH = process.env.GRPC_DEFAULT_SSL_ROOTS_FILE_PATH;
|
||||||
|
|
||||||
|
let defaultRootsData: Buffer | null = null;
|
||||||
|
|
||||||
|
export function getDefaultRootsData(): Buffer | null {
|
||||||
|
if (DEFAULT_ROOTS_FILE_PATH) {
|
||||||
|
if (defaultRootsData === null) {
|
||||||
|
defaultRootsData = fs.readFileSync(DEFAULT_ROOTS_FILE_PATH);
|
||||||
|
}
|
||||||
|
return defaultRootsData;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
@ -67,6 +67,63 @@ describe('Name Resolver', () => {
|
||||||
const resolver = resolverManager.createResolver(target, listener);
|
const resolver = resolverManager.createResolver(target, listener);
|
||||||
resolver.updateResolution();
|
resolver.updateResolution();
|
||||||
});
|
});
|
||||||
|
it('Should correctly represent an ipv4 address', done => {
|
||||||
|
const target = '1.2.3.4';
|
||||||
|
const listener: resolverManager.ResolverListener = {
|
||||||
|
onSuccessfulResolution: (
|
||||||
|
addressList: string[],
|
||||||
|
serviceConfig: ServiceConfig | null,
|
||||||
|
serviceConfigError: StatusObject | null
|
||||||
|
) => {
|
||||||
|
assert(addressList.includes('1.2.3.4:443'));
|
||||||
|
// We would check for the IPv6 address but it needs to be omitted on some Node versions
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
onError: (error: StatusObject) => {
|
||||||
|
done(new Error(`Failed with status ${error.details}`));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const resolver = resolverManager.createResolver(target, listener);
|
||||||
|
resolver.updateResolution();
|
||||||
|
});
|
||||||
|
it('Should correctly represent an ipv6 address', done => {
|
||||||
|
const target = '::1';
|
||||||
|
const listener: resolverManager.ResolverListener = {
|
||||||
|
onSuccessfulResolution: (
|
||||||
|
addressList: string[],
|
||||||
|
serviceConfig: ServiceConfig | null,
|
||||||
|
serviceConfigError: StatusObject | null
|
||||||
|
) => {
|
||||||
|
assert(addressList.includes('[::1]:443'));
|
||||||
|
// We would check for the IPv6 address but it needs to be omitted on some Node versions
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
onError: (error: StatusObject) => {
|
||||||
|
done(new Error(`Failed with status ${error.details}`));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const resolver = resolverManager.createResolver(target, listener);
|
||||||
|
resolver.updateResolution();
|
||||||
|
});
|
||||||
|
it('Should correctly represent a bracketed ipv6 address', done => {
|
||||||
|
const target = '[::1]:50051';
|
||||||
|
const listener: resolverManager.ResolverListener = {
|
||||||
|
onSuccessfulResolution: (
|
||||||
|
addressList: string[],
|
||||||
|
serviceConfig: ServiceConfig | null,
|
||||||
|
serviceConfigError: StatusObject | null
|
||||||
|
) => {
|
||||||
|
assert(addressList.includes('[::1]:50051'));
|
||||||
|
// We would check for the IPv6 address but it needs to be omitted on some Node versions
|
||||||
|
done();
|
||||||
|
},
|
||||||
|
onError: (error: StatusObject) => {
|
||||||
|
done(new Error(`Failed with status ${error.details}`));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const resolver = resolverManager.createResolver(target, listener);
|
||||||
|
resolver.updateResolution();
|
||||||
|
});
|
||||||
it('Should resolve a public address', done => {
|
it('Should resolve a public address', done => {
|
||||||
const target = 'example.com';
|
const target = 'example.com';
|
||||||
const listener: resolverManager.ResolverListener = {
|
const listener: resolverManager.ResolverListener = {
|
||||||
|
|
@ -194,4 +251,23 @@ describe('Name Resolver', () => {
|
||||||
resolver.updateResolution();
|
resolver.updateResolution();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('getDefaultAuthority', () => {
|
||||||
|
class OtherResolver implements resolverManager.Resolver {
|
||||||
|
updateResolution() {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDefaultAuthority(target: string): string {
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
it('Should return the correct authority if a different resolver has been registered', () => {
|
||||||
|
const target = 'other://name';
|
||||||
|
resolverManager.registerResolver('other:', OtherResolver);
|
||||||
|
|
||||||
|
const authority = resolverManager.getDefaultAuthority(target);
|
||||||
|
assert.equal(authority, 'other');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -41,24 +41,16 @@ describe('Server Credentials', () => {
|
||||||
const creds = ServerCredentials.createSsl(ca, []);
|
const creds = ServerCredentials.createSsl(ca, []);
|
||||||
|
|
||||||
assert.strictEqual(creds._isSecure(), true);
|
assert.strictEqual(creds._isSecure(), true);
|
||||||
assert.deepStrictEqual(creds._getSettings(), {
|
assert.strictEqual(creds._getSettings()?.ca, ca);
|
||||||
ca,
|
|
||||||
cert: [],
|
|
||||||
key: [],
|
|
||||||
requestCert: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts a boolean as the third argument', () => {
|
it('accepts a boolean as the third argument', () => {
|
||||||
const creds = ServerCredentials.createSsl(ca, [], true);
|
const creds = ServerCredentials.createSsl(ca, [], true);
|
||||||
|
|
||||||
assert.strictEqual(creds._isSecure(), true);
|
assert.strictEqual(creds._isSecure(), true);
|
||||||
assert.deepStrictEqual(creds._getSettings(), {
|
const settings = creds._getSettings();
|
||||||
ca,
|
assert.strictEqual(settings?.ca, ca);
|
||||||
cert: [],
|
assert.strictEqual(settings?.requestCert, true);
|
||||||
key: [],
|
|
||||||
requestCert: true,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts an object with two buffers in the second argument', () => {
|
it('accepts an object with two buffers in the second argument', () => {
|
||||||
|
|
@ -66,12 +58,9 @@ describe('Server Credentials', () => {
|
||||||
const creds = ServerCredentials.createSsl(null, keyCertPairs);
|
const creds = ServerCredentials.createSsl(null, keyCertPairs);
|
||||||
|
|
||||||
assert.strictEqual(creds._isSecure(), true);
|
assert.strictEqual(creds._isSecure(), true);
|
||||||
assert.deepStrictEqual(creds._getSettings(), {
|
const settings = creds._getSettings();
|
||||||
ca: undefined,
|
assert.deepStrictEqual(settings?.cert, [cert]);
|
||||||
cert: [cert],
|
assert.deepStrictEqual(settings?.key, [key]);
|
||||||
key: [key],
|
|
||||||
requestCert: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('accepts multiple objects in the second argument', () => {
|
it('accepts multiple objects in the second argument', () => {
|
||||||
|
|
@ -82,12 +71,9 @@ describe('Server Credentials', () => {
|
||||||
const creds = ServerCredentials.createSsl(null, keyCertPairs, false);
|
const creds = ServerCredentials.createSsl(null, keyCertPairs, false);
|
||||||
|
|
||||||
assert.strictEqual(creds._isSecure(), true);
|
assert.strictEqual(creds._isSecure(), true);
|
||||||
assert.deepStrictEqual(creds._getSettings(), {
|
const settings = creds._getSettings();
|
||||||
ca: undefined,
|
assert.deepStrictEqual(settings?.cert, [cert, cert]);
|
||||||
cert: [cert, cert],
|
assert.deepStrictEqual(settings?.key, [key, key]);
|
||||||
key: [key, key],
|
|
||||||
requestCert: false,
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('fails if the second argument is not an Array', () => {
|
it('fails if the second argument is not an Array', () => {
|
||||||
|
|
|
||||||
|
|
@ -699,6 +699,18 @@ describe('Other conditions', () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('for an error message with a comma', done => {
|
||||||
|
client.unary(
|
||||||
|
{ error: true, message: 'an error message, with a comma' },
|
||||||
|
(err: ServiceError, data: any) => {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, grpc.status.UNKNOWN);
|
||||||
|
assert.strictEqual(err.details, 'an error message, with a comma');
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -182,25 +182,25 @@ exports.createFromMetadataGenerator = function(metadata_generator) {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function getAuthorizationHeaderFromGoogleCredential(google_credential, url, callback) {
|
function getHeadersFromGoogleCredential(google_credential, url, callback) {
|
||||||
// google-auth-library pre-v2.0.0 does not have getRequestHeaders
|
// google-auth-library pre-v2.0.0 does not have getRequestHeaders
|
||||||
// but has getRequestMetadata, which is deprecated in v2.0.0
|
// but has getRequestMetadata, which is deprecated in v2.0.0
|
||||||
if (typeof google_credential.getRequestHeaders === 'function') {
|
if (typeof google_credential.getRequestHeaders === 'function') {
|
||||||
google_credential.getRequestHeaders(url)
|
google_credential.getRequestHeaders(url)
|
||||||
.then(function(header) {
|
.then(function(headers) {
|
||||||
callback(null, header.Authorization);
|
callback(null, headers);
|
||||||
})
|
})
|
||||||
.catch(function(err) {
|
.catch(function(err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
google_credential.getRequestMetadata(url, function(err, header) {
|
google_credential.getRequestMetadata(url, function(err, headers) {
|
||||||
if (err) {
|
if (err) {
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
callback(null, header.Authorization);
|
callback(null, headers);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -216,15 +216,17 @@ function getAuthorizationHeaderFromGoogleCredential(google_credential, url, call
|
||||||
exports.createFromGoogleCredential = function(google_credential) {
|
exports.createFromGoogleCredential = function(google_credential) {
|
||||||
return exports.createFromMetadataGenerator(function(auth_context, callback) {
|
return exports.createFromMetadataGenerator(function(auth_context, callback) {
|
||||||
var service_url = auth_context.service_url;
|
var service_url = auth_context.service_url;
|
||||||
getAuthorizationHeaderFromGoogleCredential(google_credential, service_url,
|
getHeadersFromGoogleCredential(google_credential, service_url,
|
||||||
function(err, authHeader) {
|
function(err, headers) {
|
||||||
if (err) {
|
if (err) {
|
||||||
common.log(constants.logVerbosity.INFO, 'Auth error:' + err);
|
common.log(constants.logVerbosity.INFO, 'Auth error:' + err);
|
||||||
callback(err);
|
callback(err);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var metadata = new Metadata();
|
var metadata = new Metadata();
|
||||||
metadata.add('authorization', authHeader);
|
for (const key of Object.keys(headers)) {
|
||||||
|
metadata.add(key, headers[key]);
|
||||||
|
}
|
||||||
callback(null, metadata);
|
callback(null, metadata);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,9 @@ var clone = require('lodash.clone');
|
||||||
|
|
||||||
var grpc = require('./grpc_extension');
|
var grpc = require('./grpc_extension');
|
||||||
|
|
||||||
|
const common = require('./common');
|
||||||
|
const logVerbosity = require('./constants').logVerbosity;
|
||||||
|
|
||||||
const IDEMPOTENT_REQUEST_FLAG = 0x10;
|
const IDEMPOTENT_REQUEST_FLAG = 0x10;
|
||||||
const WAIT_FOR_READY_FLAG = 0x20;
|
const WAIT_FOR_READY_FLAG = 0x20;
|
||||||
const CACHEABLE_REQUEST_FLAG = 0x40;
|
const CACHEABLE_REQUEST_FLAG = 0x40;
|
||||||
|
|
@ -231,6 +234,12 @@ Metadata._fromCoreRepresentation = function(metadata) {
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
Object.keys(metadata.metadata).forEach(key => {
|
Object.keys(metadata.metadata).forEach(key => {
|
||||||
const value = metadata.metadata[key];
|
const value = metadata.metadata[key];
|
||||||
|
if (!grpc.metadataKeyIsLegal(key)) {
|
||||||
|
common.log(logVerbosity.ERROR,
|
||||||
|
"Warning: possibly corrupted metadata key received: " +
|
||||||
|
key + ": " + value +
|
||||||
|
". Please report this at https://github.com/grpc/grpc-node/issues/1173.");
|
||||||
|
}
|
||||||
newMetadata._internal_repr[key] = clone(value);
|
newMetadata._internal_repr[key] = clone(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
set arch_list=ia32 x64
|
set arch_list=ia32 x64
|
||||||
|
|
||||||
set electron_versions=1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0 4.2.0 5.0.0 6.0.0 7.0.0
|
set electron_versions=1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0 4.2.0 5.0.0 6.0.0 6.1.0 7.0.0 7.1.0
|
||||||
|
|
||||||
set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm
|
set PATH=%PATH%;C:\Program Files\nodejs\;%APPDATA%\npm
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
set -ex
|
set -ex
|
||||||
|
|
||||||
arch_list=( ia32 x64 )
|
arch_list=( ia32 x64 )
|
||||||
electron_versions=( 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0 4.2.0 5.0.0 6.0.0 7.0.0 )
|
electron_versions=( 1.0.0 1.1.0 1.2.0 1.3.0 1.4.0 1.5.0 1.6.0 1.7.0 1.8.0 2.0.0 3.0.0 3.1.0 4.1.0 4.2.0 5.0.0 6.0.0 6.1.0 7.0.0 7.1.0 )
|
||||||
|
|
||||||
umask 022
|
umask 022
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "grpc-tools",
|
"name": "grpc-tools",
|
||||||
"version": "1.8.0",
|
"version": "1.8.1",
|
||||||
"author": "Google Inc.",
|
"author": "Google Inc.",
|
||||||
"description": "Tools for developing with gRPC on Node.js",
|
"description": "Tools for developing with gRPC on Node.js",
|
||||||
"homepage": "https://grpc.io/",
|
"homepage": "https://grpc.io/",
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,7 @@ void PrintMethod(const MethodDescriptor* method, Printer* out) {
|
||||||
void PrintService(const ServiceDescriptor* service, Printer* out,
|
void PrintService(const ServiceDescriptor* service, Printer* out,
|
||||||
const Parameters& params) {
|
const Parameters& params) {
|
||||||
map<grpc::string, grpc::string> template_vars;
|
map<grpc::string, grpc::string> template_vars;
|
||||||
out->Print(GetNodeComments(service, true).c_str());
|
out->PrintRaw(GetNodeComments(service, true).c_str());
|
||||||
template_vars["name"] = service->name();
|
template_vars["name"] = service->name();
|
||||||
template_vars["full_name"] = service->full_name();
|
template_vars["full_name"] = service->full_name();
|
||||||
if (params.generate_package_definition) {
|
if (params.generate_package_definition) {
|
||||||
|
|
@ -193,11 +193,11 @@ void PrintService(const ServiceDescriptor* service, Printer* out,
|
||||||
for (int i = 0; i < service->method_count(); i++) {
|
for (int i = 0; i < service->method_count(); i++) {
|
||||||
grpc::string method_name =
|
grpc::string method_name =
|
||||||
grpc_generator::LowercaseFirstLetter(service->method(i)->name());
|
grpc_generator::LowercaseFirstLetter(service->method(i)->name());
|
||||||
out->Print(GetNodeComments(service->method(i), true).c_str());
|
out->PrintRaw(GetNodeComments(service->method(i), true).c_str());
|
||||||
out->Print("$method_name$: ", "method_name", method_name);
|
out->Print("$method_name$: ", "method_name", method_name);
|
||||||
PrintMethod(service->method(i), out);
|
PrintMethod(service->method(i), out);
|
||||||
out->Print(",\n");
|
out->Print(",\n");
|
||||||
out->Print(GetNodeComments(service->method(i), false).c_str());
|
out->PrintRaw(GetNodeComments(service->method(i), false).c_str());
|
||||||
}
|
}
|
||||||
out->Outdent();
|
out->Outdent();
|
||||||
out->Print("};\n\n");
|
out->Print("};\n\n");
|
||||||
|
|
@ -206,7 +206,7 @@ void PrintService(const ServiceDescriptor* service, Printer* out,
|
||||||
"exports.$name$Client = "
|
"exports.$name$Client = "
|
||||||
"grpc.makeGenericClientConstructor($name$Service);\n");
|
"grpc.makeGenericClientConstructor($name$Service);\n");
|
||||||
}
|
}
|
||||||
out->Print(GetNodeComments(service, false).c_str());
|
out->PrintRaw(GetNodeComments(service, false).c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PrintImports(const FileDescriptor* file, Printer* out,
|
void PrintImports(const FileDescriptor* file, Printer* out,
|
||||||
|
|
@ -276,7 +276,7 @@ grpc::string GenerateFile(const FileDescriptor* file,
|
||||||
|
|
||||||
PrintServices(file, &out, params);
|
PrintServices(file, &out, params);
|
||||||
|
|
||||||
out.Print(GetNodeComments(file, false).c_str());
|
out.PrintRaw(GetNodeComments(file, false).c_str());
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,7 +43,6 @@ class NodeGrpcGenerator : public grpc::protobuf::compiler::CodeGenerator {
|
||||||
grpc_generator::tokenize(parameter, ",");
|
grpc_generator::tokenize(parameter, ",");
|
||||||
for (auto parameter_string = parameters_list.begin();
|
for (auto parameter_string = parameters_list.begin();
|
||||||
parameter_string != parameters_list.end(); parameter_string++) {
|
parameter_string != parameters_list.end(); parameter_string++) {
|
||||||
printf("%s", parameter_string);
|
|
||||||
if (*parameter_string == "generate_package_definition") {
|
if (*parameter_string == "generate_package_definition") {
|
||||||
generator_parameters.generate_package_definition = true;
|
generator_parameters.generate_package_definition = true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -553,6 +553,14 @@ describe(`${anyGrpc.clientName} client -> ${anyGrpc.serverName} server`, functio
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('for an error message with a comma', function(done) {
|
||||||
|
client.unary({error: true, message: 'a message, with a comma'}, function(err, data) {
|
||||||
|
assert(err);
|
||||||
|
assert.strictEqual(err.code, clientGrpc.status.UNKNOWN);
|
||||||
|
assert.strictEqual(err.details, 'a message, with a comma');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -17,7 +17,6 @@
|
||||||
cd /d %~dp0
|
cd /d %~dp0
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
git submodule update --init
|
git submodule update --init --recursive
|
||||||
git submodule foreach --recursive git submodule update --init
|
|
||||||
|
|
||||||
.\run-tests.bat
|
.\run-tests.bat
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
# Copyright 2017 gRPC authors.
|
# Copyright 2017 gRPC authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
@ -13,12 +13,14 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Deleting Ruby.
|
||||||
|
rm -rf ~/.rvm
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
cd $(dirname $0)/..
|
cd $(dirname $0)/..
|
||||||
|
|
||||||
# Install gRPC and its submodules.
|
# Install gRPC and its submodules.
|
||||||
git submodule update --init
|
git submodule update --init --recursive
|
||||||
git submodule foreach --recursive git submodule update --init
|
|
||||||
|
|
||||||
./packages/grpc-native-core/tools/buildgen/generate_projects.sh
|
./packages/grpc-native-core/tools/buildgen/generate_projects.sh
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ call npm install -g node-gyp@3
|
||||||
cd /d %~dp0
|
cd /d %~dp0
|
||||||
cd ..\..
|
cd ..\..
|
||||||
|
|
||||||
git submodule update --init
|
git submodule update --init --recursive
|
||||||
git submodule foreach --recursive git submodule update --init
|
|
||||||
|
|
||||||
set ARTIFACTS_OUT=artifacts
|
set ARTIFACTS_OUT=artifacts
|
||||||
cd packages\grpc-native-core
|
cd packages\grpc-native-core
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
# Copyright 2018 gRPC authors.
|
# Copyright 2018 gRPC authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
@ -13,6 +13,9 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Deleting Ruby.
|
||||||
|
rm -rf ~/.rvm
|
||||||
|
|
||||||
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
|
||||||
export NVM_DIR="$HOME/.nvm"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
|
|
@ -28,8 +31,7 @@ cd $(dirname $0)/../..
|
||||||
base_dir=$(pwd)
|
base_dir=$(pwd)
|
||||||
|
|
||||||
# Install gRPC and its submodules.
|
# Install gRPC and its submodules.
|
||||||
git submodule update --init
|
git submodule update --init --recursive
|
||||||
git submodule foreach --recursive git submodule update --init
|
|
||||||
|
|
||||||
pip install mako
|
pip install mako
|
||||||
./packages/grpc-native-core/tools/buildgen/generate_projects.sh
|
./packages/grpc-native-core/tools/buildgen/generate_projects.sh
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,9 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Deleting Ruby.
|
||||||
|
rm -rf ~/.rvm
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
cd $(dirname $0)/../..
|
cd $(dirname $0)/../..
|
||||||
base_dir=$(pwd)
|
base_dir=$(pwd)
|
||||||
|
|
|
||||||
|
|
@ -27,8 +27,7 @@ call npm install -g node-gyp@3
|
||||||
cd /d %~dp0
|
cd /d %~dp0
|
||||||
cd ..\..
|
cd ..\..
|
||||||
|
|
||||||
git submodule update --init
|
git submodule update --init --recursive
|
||||||
git submodule foreach --recursive git submodule update --init
|
|
||||||
|
|
||||||
set ARTIFACTS_OUT=%cd%\artifacts
|
set ARTIFACTS_OUT=%cd%\artifacts
|
||||||
cd packages\grpc-native-core
|
cd packages\grpc-native-core
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/bash
|
||||||
# Copyright 2018 gRPC authors.
|
# Copyright 2018 gRPC authors.
|
||||||
#
|
#
|
||||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
@ -13,6 +13,9 @@
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
# Deleting Ruby.
|
||||||
|
rm -rf ~/.rvm
|
||||||
|
|
||||||
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
|
curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
|
||||||
export NVM_DIR="$HOME/.nvm"
|
export NVM_DIR="$HOME/.nvm"
|
||||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||||
|
|
@ -28,8 +31,7 @@ cd $(dirname $0)/../..
|
||||||
base_dir=$(pwd)
|
base_dir=$(pwd)
|
||||||
|
|
||||||
# Install gRPC and its submodules.
|
# Install gRPC and its submodules.
|
||||||
git submodule update --init
|
git submodule update --init --recursive
|
||||||
git submodule foreach --recursive git submodule update --init
|
|
||||||
|
|
||||||
pip install mako
|
pip install mako
|
||||||
./packages/grpc-native-core/tools/buildgen/generate_projects.sh
|
./packages/grpc-native-core/tools/buildgen/generate_projects.sh
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue