Merge branch 'master' into grpc-js_client_interceptors

This commit is contained in:
murgatroid99 2020-01-07 10:48:01 -08:00
commit 738dbf8f02
33 changed files with 418 additions and 97 deletions

View File

@ -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`

31
doc/compression.md Normal file
View File

@ -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});
```

View File

@ -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"
] ]
} }

View File

@ -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();
} }

View File

@ -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) {

View File

@ -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(

View File

@ -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;
} }

View File

@ -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 => {

View File

@ -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;

View File

@ -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) {

View File

@ -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);
} }

View File

@ -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
}); });
} }
} }

View File

@ -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);

View File

@ -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();
} }

View File

@ -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;
}

View File

@ -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');
});
});
}); });

View File

@ -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', () => {

View File

@ -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();
}
);
});
}); });
}); });

View File

@ -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);
}); });
}); });

View File

@ -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);
}); });
} }

View File

@ -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

View File

@ -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

View File

@ -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/",

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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();
});
});
}); });
}); });
}); });

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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