diff --git a/packages/grpc-js/package.json b/packages/grpc-js/package.json index fff572fa..cd1c74bb 100644 --- a/packages/grpc-js/package.json +++ b/packages/grpc-js/package.json @@ -17,14 +17,14 @@ "devDependencies": { "@types/gulp": "^4.0.6", "@types/gulp-mocha": "0.0.32", - "@types/lodash": "4.14.186", + "@types/lodash": "^4.14.186", "@types/mocha": "^5.2.6", "@types/ncp": "^2.0.1", "@types/pify": "^3.0.2", "@types/semver": "^7.3.9", "clang-format": "^1.0.55", "execa": "^2.0.3", - "gts": "^2.0.0", + "gts": "^3.1.1", "gulp": "^4.0.2", "gulp-mocha": "^6.0.0", "lodash": "^4.17.4", @@ -35,7 +35,7 @@ "rimraf": "^3.0.2", "semver": "^7.3.5", "ts-node": "^8.3.0", - "typescript": "^3.7.2" + "typescript": "^4.8.4" }, "contributors": [ { diff --git a/packages/grpc-js/src/call-credentials.ts b/packages/grpc-js/src/call-credentials.ts index bbc88a89..b98624ee 100644 --- a/packages/grpc-js/src/call-credentials.ts +++ b/packages/grpc-js/src/call-credentials.ts @@ -115,6 +115,10 @@ export abstract class CallCredentials { reject(err); return; } + if (!headers) { + reject(new Error('Headers not set by metadata plugin')); + return; + } resolve(headers); } ); diff --git a/packages/grpc-js/src/client-interceptors.ts b/packages/grpc-js/src/client-interceptors.ts index d9c88f44..d9582855 100644 --- a/packages/grpc-js/src/client-interceptors.ts +++ b/packages/grpc-js/src/client-interceptors.ts @@ -34,6 +34,7 @@ import { Channel } from './channel'; import { CallOptions } from './client'; import { CallCredentials } from './call-credentials'; import { ClientMethodDefinition } from './make-client'; +import { getErrorMessage } from './error'; /** * Error class associated with passing both interceptors and interceptor @@ -374,7 +375,7 @@ class BaseInterceptingCall implements InterceptingCallInterface { } catch (e) { this.call.cancelWithStatus( Status.INTERNAL, - `Request message serialization failure: ${e.message}` + `Request message serialization failure: ${getErrorMessage(e)}` ); return; } @@ -401,7 +402,7 @@ class BaseInterceptingCall implements InterceptingCallInterface { } catch (e) { readError = { code: Status.INTERNAL, - details: `Response message parsing error: ${e.message}`, + details: `Response message parsing error: ${getErrorMessage(e)}`, metadata: new Metadata(), }; this.call.cancelWithStatus(readError.code, readError.details); diff --git a/packages/grpc-js/src/error.ts b/packages/grpc-js/src/error.ts new file mode 100644 index 00000000..b973128a --- /dev/null +++ b/packages/grpc-js/src/error.ts @@ -0,0 +1,37 @@ +/* + * Copyright 2022 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. + * + */ + +export function getErrorMessage(error: unknown): string { + if (error instanceof Error) { + return error.message; + } else { + return String(error); + } +} + +export function getErrorCode(error: unknown): number | null { + if ( + typeof error === 'object' && + error !== null && + 'code' in error && + typeof (error as Record).code === 'number' + ) { + return (error as Record).code; + } else { + return null; + } +} \ No newline at end of file diff --git a/packages/grpc-js/src/metadata.ts b/packages/grpc-js/src/metadata.ts index 376cb491..0dddd946 100644 --- a/packages/grpc-js/src/metadata.ts +++ b/packages/grpc-js/src/metadata.ts @@ -18,6 +18,7 @@ import * as http2 from 'http2'; import { log } from './logging'; import { LogVerbosity } from './constants'; +import { getErrorMessage } from './error'; const LEGAL_KEY_REGEX = /^[0-9a-z_.-]+$/; const LEGAL_NON_BINARY_VALUE_REGEX = /^[ -~]*$/; @@ -285,7 +286,7 @@ export class Metadata { } } } catch (error) { - const message = `Failed to add metadata entry ${key}: ${values}. ${error.message}. For more information see https://github.com/grpc/grpc-node/issues/1173`; + const message = `Failed to add metadata entry ${key}: ${values}. ${getErrorMessage(error)}. For more information see https://github.com/grpc/grpc-node/issues/1173`; log(LogVerbosity.ERROR, message); } } diff --git a/packages/grpc-js/src/server-call.ts b/packages/grpc-js/src/server-call.ts index b27bb1b2..a000b8ff 100644 --- a/packages/grpc-js/src/server-call.ts +++ b/packages/grpc-js/src/server-call.ts @@ -35,6 +35,7 @@ import { ChannelOptions } from './channel-options'; import * as logging from './logging'; import { StatusObject, PartialStatusObject } from './call-interface'; import { Deadline } from './deadline'; +import { getErrorCode, getErrorMessage } from './error'; const TRACER_NAME = 'server_call'; const unzip = promisify(zlib.unzip); @@ -232,8 +233,10 @@ export class ServerWritableStreamImpl return; } } catch (err) { - err.code = Status.INTERNAL; - this.emit('error', err); + this.emit('error', { + details: getErrorMessage(err), + code: Status.INTERNAL + }); } callback(); @@ -630,8 +633,10 @@ export class Http2ServerCallStream< try { next(null, this.deserializeMessage(buffer)); } catch (err) { - err.code = Status.INTERNAL; - next(err); + next({ + details: getErrorMessage(err), + code: Status.INTERNAL + }); } } @@ -679,8 +684,10 @@ export class Http2ServerCallStream< this.write(response); this.sendStatus({ code: Status.OK, details: 'OK', metadata }); } catch (err) { - err.code = Status.INTERNAL; - this.sendError(err); + this.sendError({ + details: getErrorMessage(err), + code: Status.INTERNAL + }); } } @@ -909,21 +916,15 @@ export class Http2ServerCallStream< } catch (error) { // Ignore any remaining messages when errors occur. this.bufferedMessages.length = 0; - - if ( - !( - 'code' in error && - typeof error.code === 'number' && - Number.isInteger(error.code) && - error.code >= Status.OK && - error.code <= Status.UNAUTHENTICATED - ) - ) { - // The error code is not a valid gRPC code so its being overwritten. - error.code = Status.INTERNAL; + let code = getErrorCode(error); + if (code === null || code < Status.OK || code > Status.UNAUTHENTICATED) { + code = Status.INTERNAL } - readable.emit('error', error); + readable.emit('error', { + details: getErrorMessage(error), + code: code + }); } this.isPushPending = false; diff --git a/packages/grpc-js/src/server.ts b/packages/grpc-js/src/server.ts index 2e89f459..9f732140 100644 --- a/packages/grpc-js/src/server.ts +++ b/packages/grpc-js/src/server.ts @@ -61,6 +61,7 @@ import { import { parseUri } from './uri-parser'; import { ChannelzCallTracker, ChannelzChildrenTracker, ChannelzTrace, registerChannelzServer, registerChannelzSocket, ServerInfo, ServerRef, SocketInfo, SocketRef, TlsInfo, unregisterChannelzRef } from './channelz'; import { CipherNameAndProtocol, TLSSocket } from 'tls'; +import { getErrorCode, getErrorMessage } from './error'; const { HTTP2_HEADER_PATH @@ -814,7 +815,10 @@ export class Server { try { handler = this._retrieveHandler(headers) } catch (err) { - this._respondWithError(err, stream, channelzSessionInfo) + this._respondWithError({ + details: getErrorMessage(err), + code: getErrorCode(err) ?? undefined + }, stream, channelzSessionInfo) return } @@ -866,7 +870,10 @@ export class Server { try { handler = this._retrieveHandler(headers) } catch (err) { - this._respondWithError(err, stream, null) + this._respondWithError({ + details: getErrorMessage(err), + code: getErrorCode(err) ?? undefined + }, stream, null) return }