"use strict"; /* * 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. * */ var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) { var useValue = arguments.length > 2; for (var i = 0; i < initializers.length; i++) { value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg); } return useValue ? value : void 0; }; var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) { function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; } var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value"; var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null; var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {}); var _, done = false; for (var i = decorators.length - 1; i >= 0; i--) { var context = {}; for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p]; for (var p in contextIn.access) context.access[p] = contextIn.access[p]; context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); }; var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context); if (kind === "accessor") { if (result === void 0) continue; if (result === null || typeof result !== "object") throw new TypeError("Object expected"); if (_ = accept(result.get)) descriptor.get = _; if (_ = accept(result.set)) descriptor.set = _; if (_ = accept(result.init)) initializers.unshift(_); } else if (_ = accept(result)) { if (kind === "field") initializers.unshift(_); else descriptor[key] = _; } } if (target) Object.defineProperty(target, contextIn.name, descriptor); done = true; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Server = void 0; const http2 = require("http2"); const util = require("util"); const constants_1 = require("./constants"); const server_call_1 = require("./server-call"); const server_credentials_1 = require("./server-credentials"); const resolver_1 = require("./resolver"); const logging = require("./logging"); const subchannel_address_1 = require("./subchannel-address"); const uri_parser_1 = require("./uri-parser"); const channelz_1 = require("./channelz"); const server_interceptors_1 = require("./server-interceptors"); const UNLIMITED_CONNECTION_AGE_MS = ~(1 << 31); const KEEPALIVE_MAX_TIME_MS = ~(1 << 31); const KEEPALIVE_TIMEOUT_MS = 20000; const MAX_CONNECTION_IDLE_MS = ~(1 << 31); const { HTTP2_HEADER_PATH } = http2.constants; const TRACER_NAME = 'server'; const kMaxAge = Buffer.from('max_age'); function serverCallTrace(text) { logging.trace(constants_1.LogVerbosity.DEBUG, 'server_call', text); } function noop() { } /** * Decorator to wrap a class method with util.deprecate * @param message The message to output if the deprecated method is called * @returns */ function deprecate(message) { return function (target, context) { return util.deprecate(target, message); }; } function getUnimplementedStatusResponse(methodName) { return { code: constants_1.Status.UNIMPLEMENTED, details: `The server does not implement the method ${methodName}`, }; } function getDefaultHandler(handlerType, methodName) { const unimplementedStatusResponse = getUnimplementedStatusResponse(methodName); switch (handlerType) { case 'unary': return (call, callback) => { callback(unimplementedStatusResponse, null); }; case 'clientStream': return (call, callback) => { callback(unimplementedStatusResponse, null); }; case 'serverStream': return (call) => { call.emit('error', unimplementedStatusResponse); }; case 'bidi': return (call) => { call.emit('error', unimplementedStatusResponse); }; default: throw new Error(`Invalid handlerType ${handlerType}`); } } let Server = (() => { var _a; let _instanceExtraInitializers = []; let _start_decorators; return _a = class Server { constructor(options) { var _b, _c, _d, _e, _f, _g; this.boundPorts = (__runInitializers(this, _instanceExtraInitializers), new Map()); this.http2Servers = new Map(); this.sessionIdleTimeouts = new Map(); this.handlers = new Map(); this.sessions = new Map(); /** * This field only exists to ensure that the start method throws an error if * it is called twice, as it did previously. */ this.started = false; this.shutdown = false; this.serverAddressString = 'null'; // Channelz Info this.channelzEnabled = true; this.options = options !== null && options !== void 0 ? options : {}; if (this.options['grpc.enable_channelz'] === 0) { this.channelzEnabled = false; this.channelzTrace = new channelz_1.ChannelzTraceStub(); this.callTracker = new channelz_1.ChannelzCallTrackerStub(); this.listenerChildrenTracker = new channelz_1.ChannelzChildrenTrackerStub(); this.sessionChildrenTracker = new channelz_1.ChannelzChildrenTrackerStub(); } else { this.channelzTrace = new channelz_1.ChannelzTrace(); this.callTracker = new channelz_1.ChannelzCallTracker(); this.listenerChildrenTracker = new channelz_1.ChannelzChildrenTracker(); this.sessionChildrenTracker = new channelz_1.ChannelzChildrenTracker(); } this.channelzRef = (0, channelz_1.registerChannelzServer)('server', () => this.getChannelzInfo(), this.channelzEnabled); this.channelzTrace.addTrace('CT_INFO', 'Server created'); this.maxConnectionAgeMs = (_b = this.options['grpc.max_connection_age_ms']) !== null && _b !== void 0 ? _b : UNLIMITED_CONNECTION_AGE_MS; this.maxConnectionAgeGraceMs = (_c = this.options['grpc.max_connection_age_grace_ms']) !== null && _c !== void 0 ? _c : UNLIMITED_CONNECTION_AGE_MS; this.keepaliveTimeMs = (_d = this.options['grpc.keepalive_time_ms']) !== null && _d !== void 0 ? _d : KEEPALIVE_MAX_TIME_MS; this.keepaliveTimeoutMs = (_e = this.options['grpc.keepalive_timeout_ms']) !== null && _e !== void 0 ? _e : KEEPALIVE_TIMEOUT_MS; this.sessionIdleTimeout = (_f = this.options['grpc.max_connection_idle_ms']) !== null && _f !== void 0 ? _f : MAX_CONNECTION_IDLE_MS; this.commonServerOptions = { maxSendHeaderBlockLength: Number.MAX_SAFE_INTEGER, }; if ('grpc-node.max_session_memory' in this.options) { this.commonServerOptions.maxSessionMemory = this.options['grpc-node.max_session_memory']; } else { /* By default, set a very large max session memory limit, to effectively * disable enforcement of the limit. Some testing indicates that Node's * behavior degrades badly when this limit is reached, so we solve that * by disabling the check entirely. */ this.commonServerOptions.maxSessionMemory = Number.MAX_SAFE_INTEGER; } if ('grpc.max_concurrent_streams' in this.options) { this.commonServerOptions.settings = { maxConcurrentStreams: this.options['grpc.max_concurrent_streams'], }; } this.interceptors = (_g = this.options.interceptors) !== null && _g !== void 0 ? _g : []; this.trace('Server constructed'); } getChannelzInfo() { return { trace: this.channelzTrace, callTracker: this.callTracker, listenerChildren: this.listenerChildrenTracker.getChildLists(), sessionChildren: this.sessionChildrenTracker.getChildLists(), }; } getChannelzSessionInfo(session) { var _b, _c, _d; const sessionInfo = this.sessions.get(session); const sessionSocket = session.socket; const remoteAddress = sessionSocket.remoteAddress ? (0, subchannel_address_1.stringToSubchannelAddress)(sessionSocket.remoteAddress, sessionSocket.remotePort) : null; const localAddress = sessionSocket.localAddress ? (0, subchannel_address_1.stringToSubchannelAddress)(sessionSocket.localAddress, sessionSocket.localPort) : null; let tlsInfo; if (session.encrypted) { const tlsSocket = sessionSocket; const cipherInfo = tlsSocket.getCipher(); const certificate = tlsSocket.getCertificate(); const peerCertificate = tlsSocket.getPeerCertificate(); tlsInfo = { cipherSuiteStandardName: (_b = cipherInfo.standardName) !== null && _b !== void 0 ? _b : null, cipherSuiteOtherName: cipherInfo.standardName ? null : cipherInfo.name, localCertificate: certificate && 'raw' in certificate ? certificate.raw : null, remoteCertificate: peerCertificate && 'raw' in peerCertificate ? peerCertificate.raw : null, }; } else { tlsInfo = null; } const socketInfo = { remoteAddress: remoteAddress, localAddress: localAddress, security: tlsInfo, remoteName: null, streamsStarted: sessionInfo.streamTracker.callsStarted, streamsSucceeded: sessionInfo.streamTracker.callsSucceeded, streamsFailed: sessionInfo.streamTracker.callsFailed, messagesSent: sessionInfo.messagesSent, messagesReceived: sessionInfo.messagesReceived, keepAlivesSent: sessionInfo.keepAlivesSent, lastLocalStreamCreatedTimestamp: null, lastRemoteStreamCreatedTimestamp: sessionInfo.streamTracker.lastCallStartedTimestamp, lastMessageSentTimestamp: sessionInfo.lastMessageSentTimestamp, lastMessageReceivedTimestamp: sessionInfo.lastMessageReceivedTimestamp, localFlowControlWindow: (_c = session.state.localWindowSize) !== null && _c !== void 0 ? _c : null, remoteFlowControlWindow: (_d = session.state.remoteWindowSize) !== null && _d !== void 0 ? _d : null, }; return socketInfo; } trace(text) { logging.trace(constants_1.LogVerbosity.DEBUG, TRACER_NAME, '(' + this.channelzRef.id + ') ' + text); } keepaliveTrace(text) { logging.trace(constants_1.LogVerbosity.DEBUG, 'keepalive', '(' + this.channelzRef.id + ') ' + text); } addProtoService() { throw new Error('Not implemented. Use addService() instead'); } addService(service, implementation) { if (service === null || typeof service !== 'object' || implementation === null || typeof implementation !== 'object') { throw new Error('addService() requires two objects as arguments'); } const serviceKeys = Object.keys(service); if (serviceKeys.length === 0) { throw new Error('Cannot add an empty service to a server'); } serviceKeys.forEach(name => { const attrs = service[name]; let methodType; if (attrs.requestStream) { if (attrs.responseStream) { methodType = 'bidi'; } else { methodType = 'clientStream'; } } else { if (attrs.responseStream) { methodType = 'serverStream'; } else { methodType = 'unary'; } } let implFn = implementation[name]; let impl; if (implFn === undefined && typeof attrs.originalName === 'string') { implFn = implementation[attrs.originalName]; } if (implFn !== undefined) { impl = implFn.bind(implementation); } else { impl = getDefaultHandler(methodType, name); } const success = this.register(attrs.path, impl, attrs.responseSerialize, attrs.requestDeserialize, methodType); if (success === false) { throw new Error(`Method handler for ${attrs.path} already provided.`); } }); } removeService(service) { if (service === null || typeof service !== 'object') { throw new Error('removeService() requires object as argument'); } const serviceKeys = Object.keys(service); serviceKeys.forEach(name => { const attrs = service[name]; this.unregister(attrs.path); }); } bind(port, creds) { throw new Error('Not implemented. Use bindAsync() instead'); } /** * This API is experimental, so API stability is not guaranteed across minor versions. * @param boundAddress * @returns */ experimentalRegisterListenerToChannelz(boundAddress) { return (0, channelz_1.registerChannelzSocket)((0, subchannel_address_1.subchannelAddressToString)(boundAddress), () => { return { localAddress: boundAddress, remoteAddress: null, security: null, remoteName: null, streamsStarted: 0, streamsSucceeded: 0, streamsFailed: 0, messagesSent: 0, messagesReceived: 0, keepAlivesSent: 0, lastLocalStreamCreatedTimestamp: null, lastRemoteStreamCreatedTimestamp: null, lastMessageSentTimestamp: null, lastMessageReceivedTimestamp: null, localFlowControlWindow: null, remoteFlowControlWindow: null, }; }, this.channelzEnabled); } experimentalUnregisterListenerFromChannelz(channelzRef) { (0, channelz_1.unregisterChannelzRef)(channelzRef); } createHttp2Server(credentials) { let http2Server; if (credentials._isSecure()) { const constructorOptions = credentials._getConstructorOptions(); const contextOptions = credentials._getSecureContextOptions(); const secureServerOptions = Object.assign(Object.assign(Object.assign(Object.assign({}, this.commonServerOptions), constructorOptions), contextOptions), { enableTrace: this.options['grpc-node.tls_enable_trace'] === 1 }); let areCredentialsValid = contextOptions !== null; this.trace('Initial credentials valid: ' + areCredentialsValid); http2Server = http2.createSecureServer(secureServerOptions); http2Server.prependListener('connection', (socket) => { if (!areCredentialsValid) { this.trace('Dropped connection from ' + JSON.stringify(socket.address()) + ' due to unloaded credentials'); socket.destroy(); } }); http2Server.on('secureConnection', (socket) => { /* These errors need to be handled by the user of Http2SecureServer, * according to https://github.com/nodejs/node/issues/35824 */ socket.on('error', (e) => { this.trace('An incoming TLS connection closed with error: ' + e.message); }); }); const credsWatcher = options => { if (options) { const secureServer = http2Server; try { secureServer.setSecureContext(options); } catch (e) { logging.log(constants_1.LogVerbosity.ERROR, 'Failed to set secure context with error ' + e.message); options = null; } } areCredentialsValid = options !== null; this.trace('Post-update credentials valid: ' + areCredentialsValid); }; credentials._addWatcher(credsWatcher); http2Server.on('close', () => { credentials._removeWatcher(credsWatcher); }); } else { http2Server = http2.createServer(this.commonServerOptions); } http2Server.setTimeout(0, noop); this._setupHandlers(http2Server, credentials._getInterceptors()); return http2Server; } bindOneAddress(address, boundPortObject) { this.trace('Attempting to bind ' + (0, subchannel_address_1.subchannelAddressToString)(address)); const http2Server = this.createHttp2Server(boundPortObject.credentials); return new Promise((resolve, reject) => { const onError = (err) => { this.trace('Failed to bind ' + (0, subchannel_address_1.subchannelAddressToString)(address) + ' with error ' + err.message); resolve({ port: 'port' in address ? address.port : 1, error: err.message, }); }; http2Server.once('error', onError); http2Server.listen(address, () => { const boundAddress = http2Server.address(); let boundSubchannelAddress; if (typeof boundAddress === 'string') { boundSubchannelAddress = { path: boundAddress, }; } else { boundSubchannelAddress = { host: boundAddress.address, port: boundAddress.port, }; } const channelzRef = this.experimentalRegisterListenerToChannelz(boundSubchannelAddress); this.listenerChildrenTracker.refChild(channelzRef); this.http2Servers.set(http2Server, { channelzRef: channelzRef, sessions: new Set(), ownsChannelzRef: true }); boundPortObject.listeningServers.add(http2Server); this.trace('Successfully bound ' + (0, subchannel_address_1.subchannelAddressToString)(boundSubchannelAddress)); resolve({ port: 'port' in boundSubchannelAddress ? boundSubchannelAddress.port : 1, }); http2Server.removeListener('error', onError); }); }); } async bindManyPorts(addressList, boundPortObject) { if (addressList.length === 0) { return { count: 0, port: 0, errors: [], }; } if ((0, subchannel_address_1.isTcpSubchannelAddress)(addressList[0]) && addressList[0].port === 0) { /* If binding to port 0, first try to bind the first address, then bind * the rest of the address list to the specific port that it binds. */ const firstAddressResult = await this.bindOneAddress(addressList[0], boundPortObject); if (firstAddressResult.error) { /* If the first address fails to bind, try the same operation starting * from the second item in the list. */ const restAddressResult = await this.bindManyPorts(addressList.slice(1), boundPortObject); return Object.assign(Object.assign({}, restAddressResult), { errors: [firstAddressResult.error, ...restAddressResult.errors] }); } else { const restAddresses = addressList .slice(1) .map(address => (0, subchannel_address_1.isTcpSubchannelAddress)(address) ? { host: address.host, port: firstAddressResult.port } : address); const restAddressResult = await Promise.all(restAddresses.map(address => this.bindOneAddress(address, boundPortObject))); const allResults = [firstAddressResult, ...restAddressResult]; return { count: allResults.filter(result => result.error === undefined).length, port: firstAddressResult.port, errors: allResults .filter(result => result.error) .map(result => result.error), }; } } else { const allResults = await Promise.all(addressList.map(address => this.bindOneAddress(address, boundPortObject))); return { count: allResults.filter(result => result.error === undefined).length, port: allResults[0].port, errors: allResults .filter(result => result.error) .map(result => result.error), }; } } async bindAddressList(addressList, boundPortObject) { const bindResult = await this.bindManyPorts(addressList, boundPortObject); if (bindResult.count > 0) { if (bindResult.count < addressList.length) { logging.log(constants_1.LogVerbosity.INFO, `WARNING Only ${bindResult.count} addresses added out of total ${addressList.length} resolved`); } return bindResult.port; } else { const errorString = `No address added out of total ${addressList.length} resolved`; logging.log(constants_1.LogVerbosity.ERROR, errorString); throw new Error(`${errorString} errors: [${bindResult.errors.join(',')}]`); } } resolvePort(port) { return new Promise((resolve, reject) => { const resolverListener = { onSuccessfulResolution: (endpointList, serviceConfig, serviceConfigError) => { // We only want one resolution result. Discard all future results resolverListener.onSuccessfulResolution = () => { }; const addressList = [].concat(...endpointList.map(endpoint => endpoint.addresses)); if (addressList.length === 0) { reject(new Error(`No addresses resolved for port ${port}`)); return; } resolve(addressList); }, onError: error => { reject(new Error(error.details)); }, }; const resolver = (0, resolver_1.createResolver)(port, resolverListener, this.options); resolver.updateResolution(); }); } async bindPort(port, boundPortObject) { const addressList = await this.resolvePort(port); if (boundPortObject.cancelled) { this.completeUnbind(boundPortObject); throw new Error('bindAsync operation cancelled by unbind call'); } const portNumber = await this.bindAddressList(addressList, boundPortObject); if (boundPortObject.cancelled) { this.completeUnbind(boundPortObject); throw new Error('bindAsync operation cancelled by unbind call'); } return portNumber; } normalizePort(port) { const initialPortUri = (0, uri_parser_1.parseUri)(port); if (initialPortUri === null) { throw new Error(`Could not parse port "${port}"`); } const portUri = (0, resolver_1.mapUriDefaultScheme)(initialPortUri); if (portUri === null) { throw new Error(`Could not get a default scheme for port "${port}"`); } return portUri; } bindAsync(port, creds, callback) { if (this.shutdown) { throw new Error('bindAsync called after shutdown'); } if (typeof port !== 'string') { throw new TypeError('port must be a string'); } if (creds === null || !(creds instanceof server_credentials_1.ServerCredentials)) { throw new TypeError('creds must be a ServerCredentials object'); } if (typeof callback !== 'function') { throw new TypeError('callback must be a function'); } this.trace('bindAsync port=' + port); const portUri = this.normalizePort(port); const deferredCallback = (error, port) => { process.nextTick(() => callback(error, port)); }; /* First, if this port is already bound or that bind operation is in * progress, use that result. */ let boundPortObject = this.boundPorts.get((0, uri_parser_1.uriToString)(portUri)); if (boundPortObject) { if (!creds._equals(boundPortObject.credentials)) { deferredCallback(new Error(`${port} already bound with incompatible credentials`), 0); return; } /* If that operation has previously been cancelled by an unbind call, * uncancel it. */ boundPortObject.cancelled = false; if (boundPortObject.completionPromise) { boundPortObject.completionPromise.then(portNum => callback(null, portNum), error => callback(error, 0)); } else { deferredCallback(null, boundPortObject.portNumber); } return; } boundPortObject = { mapKey: (0, uri_parser_1.uriToString)(portUri), originalUri: portUri, completionPromise: null, cancelled: false, portNumber: 0, credentials: creds, listeningServers: new Set(), }; const splitPort = (0, uri_parser_1.splitHostPort)(portUri.path); const completionPromise = this.bindPort(portUri, boundPortObject); boundPortObject.completionPromise = completionPromise; /* If the port number is 0, defer populating the map entry until after the * bind operation completes and we have a specific port number. Otherwise, * populate it immediately. */ if ((splitPort === null || splitPort === void 0 ? void 0 : splitPort.port) === 0) { completionPromise.then(portNum => { const finalUri = { scheme: portUri.scheme, authority: portUri.authority, path: (0, uri_parser_1.combineHostPort)({ host: splitPort.host, port: portNum }), }; boundPortObject.mapKey = (0, uri_parser_1.uriToString)(finalUri); boundPortObject.completionPromise = null; boundPortObject.portNumber = portNum; this.boundPorts.set(boundPortObject.mapKey, boundPortObject); callback(null, portNum); }, error => { callback(error, 0); }); } else { this.boundPorts.set(boundPortObject.mapKey, boundPortObject); completionPromise.then(portNum => { boundPortObject.completionPromise = null; boundPortObject.portNumber = portNum; callback(null, portNum); }, error => { callback(error, 0); }); } } registerInjectorToChannelz() { return (0, channelz_1.registerChannelzSocket)('injector', () => { return { localAddress: null, remoteAddress: null, security: null, remoteName: null, streamsStarted: 0, streamsSucceeded: 0, streamsFailed: 0, messagesSent: 0, messagesReceived: 0, keepAlivesSent: 0, lastLocalStreamCreatedTimestamp: null, lastRemoteStreamCreatedTimestamp: null, lastMessageSentTimestamp: null, lastMessageReceivedTimestamp: null, localFlowControlWindow: null, remoteFlowControlWindow: null, }; }, this.channelzEnabled); } /** * This API is experimental, so API stability is not guaranteed across minor versions. * @param credentials * @param channelzRef * @returns */ experimentalCreateConnectionInjectorWithChannelzRef(credentials, channelzRef, ownsChannelzRef = false) { if (credentials === null || !(credentials instanceof server_credentials_1.ServerCredentials)) { throw new TypeError('creds must be a ServerCredentials object'); } if (this.channelzEnabled) { this.listenerChildrenTracker.refChild(channelzRef); } const server = this.createHttp2Server(credentials); const sessionsSet = new Set(); this.http2Servers.set(server, { channelzRef: channelzRef, sessions: sessionsSet, ownsChannelzRef }); return { injectConnection: (connection) => { server.emit('connection', connection); }, drain: (graceTimeMs) => { var _b, _c; for (const session of sessionsSet) { this.closeSession(session); } (_c = (_b = setTimeout(() => { for (const session of sessionsSet) { session.destroy(http2.constants.NGHTTP2_CANCEL); } }, graceTimeMs)).unref) === null || _c === void 0 ? void 0 : _c.call(_b); }, destroy: () => { this.closeServer(server); for (const session of sessionsSet) { this.closeSession(session); } } }; } createConnectionInjector(credentials) { if (credentials === null || !(credentials instanceof server_credentials_1.ServerCredentials)) { throw new TypeError('creds must be a ServerCredentials object'); } const channelzRef = this.registerInjectorToChannelz(); return this.experimentalCreateConnectionInjectorWithChannelzRef(credentials, channelzRef, true); } closeServer(server, callback) { this.trace('Closing server with address ' + JSON.stringify(server.address())); const serverInfo = this.http2Servers.get(server); server.close(() => { if (serverInfo && serverInfo.ownsChannelzRef) { this.listenerChildrenTracker.unrefChild(serverInfo.channelzRef); (0, channelz_1.unregisterChannelzRef)(serverInfo.channelzRef); } this.http2Servers.delete(server); callback === null || callback === void 0 ? void 0 : callback(); }); } closeSession(session, callback) { var _b; this.trace('Closing session initiated by ' + ((_b = session.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress)); const sessionInfo = this.sessions.get(session); const closeCallback = () => { if (sessionInfo) { this.sessionChildrenTracker.unrefChild(sessionInfo.ref); (0, channelz_1.unregisterChannelzRef)(sessionInfo.ref); } callback === null || callback === void 0 ? void 0 : callback(); }; if (session.closed) { queueMicrotask(closeCallback); } else { session.close(closeCallback); } } completeUnbind(boundPortObject) { for (const server of boundPortObject.listeningServers) { const serverInfo = this.http2Servers.get(server); this.closeServer(server, () => { boundPortObject.listeningServers.delete(server); }); if (serverInfo) { for (const session of serverInfo.sessions) { this.closeSession(session); } } } this.boundPorts.delete(boundPortObject.mapKey); } /** * Unbind a previously bound port, or cancel an in-progress bindAsync * operation. If port 0 was bound, only the actual bound port can be * unbound. For example, if bindAsync was called with "localhost:0" and the * bound port result was 54321, it can be unbound as "localhost:54321". * @param port */ unbind(port) { this.trace('unbind port=' + port); const portUri = this.normalizePort(port); const splitPort = (0, uri_parser_1.splitHostPort)(portUri.path); if ((splitPort === null || splitPort === void 0 ? void 0 : splitPort.port) === 0) { throw new Error('Cannot unbind port 0'); } const boundPortObject = this.boundPorts.get((0, uri_parser_1.uriToString)(portUri)); if (boundPortObject) { this.trace('unbinding ' + boundPortObject.mapKey + ' originally bound as ' + (0, uri_parser_1.uriToString)(boundPortObject.originalUri)); /* If the bind operation is pending, the cancelled flag will trigger * the unbind operation later. */ if (boundPortObject.completionPromise) { boundPortObject.cancelled = true; } else { this.completeUnbind(boundPortObject); } } } /** * Gracefully close all connections associated with a previously bound port. * After the grace time, forcefully close all remaining open connections. * * If port 0 was bound, only the actual bound port can be * drained. For example, if bindAsync was called with "localhost:0" and the * bound port result was 54321, it can be drained as "localhost:54321". * @param port * @param graceTimeMs * @returns */ drain(port, graceTimeMs) { var _b, _c; this.trace('drain port=' + port + ' graceTimeMs=' + graceTimeMs); const portUri = this.normalizePort(port); const splitPort = (0, uri_parser_1.splitHostPort)(portUri.path); if ((splitPort === null || splitPort === void 0 ? void 0 : splitPort.port) === 0) { throw new Error('Cannot drain port 0'); } const boundPortObject = this.boundPorts.get((0, uri_parser_1.uriToString)(portUri)); if (!boundPortObject) { return; } const allSessions = new Set(); for (const http2Server of boundPortObject.listeningServers) { const serverEntry = this.http2Servers.get(http2Server); if (serverEntry) { for (const session of serverEntry.sessions) { allSessions.add(session); this.closeSession(session, () => { allSessions.delete(session); }); } } } /* After the grace time ends, send another goaway to all remaining sessions * with the CANCEL code. */ (_c = (_b = setTimeout(() => { for (const session of allSessions) { session.destroy(http2.constants.NGHTTP2_CANCEL); } }, graceTimeMs)).unref) === null || _c === void 0 ? void 0 : _c.call(_b); } forceShutdown() { for (const boundPortObject of this.boundPorts.values()) { boundPortObject.cancelled = true; } this.boundPorts.clear(); // Close the server if it is still running. for (const server of this.http2Servers.keys()) { this.closeServer(server); } // Always destroy any available sessions. It's possible that one or more // tryShutdown() calls are in progress. Don't wait on them to finish. this.sessions.forEach((channelzInfo, session) => { this.closeSession(session); // Cast NGHTTP2_CANCEL to any because TypeScript doesn't seem to // recognize destroy(code) as a valid signature. // eslint-disable-next-line @typescript-eslint/no-explicit-any session.destroy(http2.constants.NGHTTP2_CANCEL); }); this.sessions.clear(); (0, channelz_1.unregisterChannelzRef)(this.channelzRef); this.shutdown = true; } register(name, handler, serialize, deserialize, type) { if (this.handlers.has(name)) { return false; } this.handlers.set(name, { func: handler, serialize, deserialize, type, path: name, }); return true; } unregister(name) { return this.handlers.delete(name); } /** * @deprecated No longer needed as of version 1.10.x */ start() { if (this.http2Servers.size === 0 || [...this.http2Servers.keys()].every(server => !server.listening)) { throw new Error('server must be bound in order to start'); } if (this.started === true) { throw new Error('server is already started'); } this.started = true; } tryShutdown(callback) { var _b; const wrappedCallback = (error) => { (0, channelz_1.unregisterChannelzRef)(this.channelzRef); callback(error); }; let pendingChecks = 0; function maybeCallback() { pendingChecks--; if (pendingChecks === 0) { wrappedCallback(); } } this.shutdown = true; for (const [serverKey, server] of this.http2Servers.entries()) { pendingChecks++; const serverString = server.channelzRef.name; this.trace('Waiting for server ' + serverString + ' to close'); this.closeServer(serverKey, () => { this.trace('Server ' + serverString + ' finished closing'); maybeCallback(); }); for (const session of server.sessions.keys()) { pendingChecks++; const sessionString = (_b = session.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress; this.trace('Waiting for session ' + sessionString + ' to close'); this.closeSession(session, () => { this.trace('Session ' + sessionString + ' finished closing'); maybeCallback(); }); } } if (pendingChecks === 0) { wrappedCallback(); } } addHttp2Port() { throw new Error('Not yet implemented'); } /** * Get the channelz reference object for this server. The returned value is * garbage if channelz is disabled for this server. * @returns */ getChannelzRef() { return this.channelzRef; } _verifyContentType(stream, headers) { const contentType = headers[http2.constants.HTTP2_HEADER_CONTENT_TYPE]; if (typeof contentType !== 'string' || !contentType.startsWith('application/grpc')) { stream.respond({ [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, }, { endStream: true }); return false; } return true; } _retrieveHandler(path) { serverCallTrace('Received call to method ' + path + ' at address ' + this.serverAddressString); const handler = this.handlers.get(path); if (handler === undefined) { serverCallTrace('No handler registered for method ' + path + '. Sending UNIMPLEMENTED status.'); return null; } return handler; } _respondWithError(err, stream, channelzSessionInfo = null) { var _b, _c; const trailersToSend = Object.assign({ 'grpc-status': (_b = err.code) !== null && _b !== void 0 ? _b : constants_1.Status.INTERNAL, 'grpc-message': err.details, [http2.constants.HTTP2_HEADER_STATUS]: http2.constants.HTTP_STATUS_OK, [http2.constants.HTTP2_HEADER_CONTENT_TYPE]: 'application/grpc+proto' }, (_c = err.metadata) === null || _c === void 0 ? void 0 : _c.toHttp2Headers()); stream.respond(trailersToSend, { endStream: true }); this.callTracker.addCallFailed(); channelzSessionInfo === null || channelzSessionInfo === void 0 ? void 0 : channelzSessionInfo.streamTracker.addCallFailed(); } _channelzHandler(extraInterceptors, stream, headers) { // for handling idle timeout this.onStreamOpened(stream); const channelzSessionInfo = this.sessions.get(stream.session); this.callTracker.addCallStarted(); channelzSessionInfo === null || channelzSessionInfo === void 0 ? void 0 : channelzSessionInfo.streamTracker.addCallStarted(); if (!this._verifyContentType(stream, headers)) { this.callTracker.addCallFailed(); channelzSessionInfo === null || channelzSessionInfo === void 0 ? void 0 : channelzSessionInfo.streamTracker.addCallFailed(); return; } const path = headers[HTTP2_HEADER_PATH]; const handler = this._retrieveHandler(path); if (!handler) { this._respondWithError(getUnimplementedStatusResponse(path), stream, channelzSessionInfo); return; } const callEventTracker = { addMessageSent: () => { if (channelzSessionInfo) { channelzSessionInfo.messagesSent += 1; channelzSessionInfo.lastMessageSentTimestamp = new Date(); } }, addMessageReceived: () => { if (channelzSessionInfo) { channelzSessionInfo.messagesReceived += 1; channelzSessionInfo.lastMessageReceivedTimestamp = new Date(); } }, onCallEnd: status => { if (status.code === constants_1.Status.OK) { this.callTracker.addCallSucceeded(); } else { this.callTracker.addCallFailed(); } }, onStreamEnd: success => { if (channelzSessionInfo) { if (success) { channelzSessionInfo.streamTracker.addCallSucceeded(); } else { channelzSessionInfo.streamTracker.addCallFailed(); } } }, }; const call = (0, server_interceptors_1.getServerInterceptingCall)([...extraInterceptors, ...this.interceptors], stream, headers, callEventTracker, handler, this.options); if (!this._runHandlerForCall(call, handler)) { this.callTracker.addCallFailed(); channelzSessionInfo === null || channelzSessionInfo === void 0 ? void 0 : channelzSessionInfo.streamTracker.addCallFailed(); call.sendStatus({ code: constants_1.Status.INTERNAL, details: `Unknown handler type: ${handler.type}`, }); } } _streamHandler(extraInterceptors, stream, headers) { // for handling idle timeout this.onStreamOpened(stream); if (this._verifyContentType(stream, headers) !== true) { return; } const path = headers[HTTP2_HEADER_PATH]; const handler = this._retrieveHandler(path); if (!handler) { this._respondWithError(getUnimplementedStatusResponse(path), stream, null); return; } const call = (0, server_interceptors_1.getServerInterceptingCall)([...extraInterceptors, ...this.interceptors], stream, headers, null, handler, this.options); if (!this._runHandlerForCall(call, handler)) { call.sendStatus({ code: constants_1.Status.INTERNAL, details: `Unknown handler type: ${handler.type}`, }); } } _runHandlerForCall(call, handler) { const { type } = handler; if (type === 'unary') { handleUnary(call, handler); } else if (type === 'clientStream') { handleClientStreaming(call, handler); } else if (type === 'serverStream') { handleServerStreaming(call, handler); } else if (type === 'bidi') { handleBidiStreaming(call, handler); } else { return false; } return true; } _setupHandlers(http2Server, extraInterceptors) { if (http2Server === null) { return; } const serverAddress = http2Server.address(); let serverAddressString = 'null'; if (serverAddress) { if (typeof serverAddress === 'string') { serverAddressString = serverAddress; } else { serverAddressString = serverAddress.address + ':' + serverAddress.port; } } this.serverAddressString = serverAddressString; const handler = this.channelzEnabled ? this._channelzHandler : this._streamHandler; const sessionHandler = this.channelzEnabled ? this._channelzSessionHandler(http2Server) : this._sessionHandler(http2Server); http2Server.on('stream', handler.bind(this, extraInterceptors)); http2Server.on('session', sessionHandler); } _sessionHandler(http2Server) { return (session) => { var _b, _c; (_b = this.http2Servers.get(http2Server)) === null || _b === void 0 ? void 0 : _b.sessions.add(session); let connectionAgeTimer = null; let connectionAgeGraceTimer = null; let keepaliveTimer = null; let sessionClosedByServer = false; const idleTimeoutObj = this.enableIdleTimeout(session); if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { // Apply a random jitter within a +/-10% range const jitterMagnitude = this.maxConnectionAgeMs / 10; const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude; connectionAgeTimer = setTimeout(() => { var _b, _c; sessionClosedByServer = true; this.trace('Connection dropped by max connection age: ' + ((_b = session.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress)); try { session.goaway(http2.constants.NGHTTP2_NO_ERROR, ~(1 << 31), kMaxAge); } catch (e) { // The goaway can't be sent because the session is already closed session.destroy(); return; } session.close(); /* Allow a grace period after sending the GOAWAY before forcibly * closing the connection. */ if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { connectionAgeGraceTimer = setTimeout(() => { session.destroy(); }, this.maxConnectionAgeGraceMs); (_c = connectionAgeGraceTimer.unref) === null || _c === void 0 ? void 0 : _c.call(connectionAgeGraceTimer); } }, this.maxConnectionAgeMs + jitter); (_c = connectionAgeTimer.unref) === null || _c === void 0 ? void 0 : _c.call(connectionAgeTimer); } const clearKeepaliveTimeout = () => { if (keepaliveTimer) { clearTimeout(keepaliveTimer); keepaliveTimer = null; } }; const canSendPing = () => { return (!session.destroyed && this.keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS && this.keepaliveTimeMs > 0); }; /* eslint-disable-next-line prefer-const */ let sendPing; // hoisted for use in maybeStartKeepalivePingTimer const maybeStartKeepalivePingTimer = () => { var _b; if (!canSendPing()) { return; } this.keepaliveTrace('Starting keepalive timer for ' + this.keepaliveTimeMs + 'ms'); keepaliveTimer = setTimeout(() => { clearKeepaliveTimeout(); sendPing(); }, this.keepaliveTimeMs); (_b = keepaliveTimer.unref) === null || _b === void 0 ? void 0 : _b.call(keepaliveTimer); }; sendPing = () => { var _b; if (!canSendPing()) { return; } this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); let pingSendError = ''; try { const pingSentSuccessfully = session.ping((err, duration, payload) => { clearKeepaliveTimeout(); if (err) { this.keepaliveTrace('Ping failed with error: ' + err.message); sessionClosedByServer = true; session.close(); } else { this.keepaliveTrace('Received ping response'); maybeStartKeepalivePingTimer(); } }); if (!pingSentSuccessfully) { pingSendError = 'Ping returned false'; } } catch (e) { // grpc/grpc-node#2139 pingSendError = (e instanceof Error ? e.message : '') || 'Unknown error'; } if (pingSendError) { this.keepaliveTrace('Ping send failed: ' + pingSendError); this.trace('Connection dropped due to ping send error: ' + pingSendError); sessionClosedByServer = true; session.close(); return; } keepaliveTimer = setTimeout(() => { clearKeepaliveTimeout(); this.keepaliveTrace('Ping timeout passed without response'); this.trace('Connection dropped by keepalive timeout'); sessionClosedByServer = true; session.close(); }, this.keepaliveTimeoutMs); (_b = keepaliveTimer.unref) === null || _b === void 0 ? void 0 : _b.call(keepaliveTimer); }; maybeStartKeepalivePingTimer(); session.on('close', () => { var _b, _c; if (!sessionClosedByServer) { this.trace(`Connection dropped by client ${(_b = session.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress}`); } if (connectionAgeTimer) { clearTimeout(connectionAgeTimer); } if (connectionAgeGraceTimer) { clearTimeout(connectionAgeGraceTimer); } clearKeepaliveTimeout(); if (idleTimeoutObj !== null) { clearTimeout(idleTimeoutObj.timeout); this.sessionIdleTimeouts.delete(session); } (_c = this.http2Servers.get(http2Server)) === null || _c === void 0 ? void 0 : _c.sessions.delete(session); }); }; } _channelzSessionHandler(http2Server) { return (session) => { var _b, _c, _d, _e; const channelzRef = (0, channelz_1.registerChannelzSocket)((_c = (_b = session.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress) !== null && _c !== void 0 ? _c : 'unknown', this.getChannelzSessionInfo.bind(this, session), this.channelzEnabled); const channelzSessionInfo = { ref: channelzRef, streamTracker: new channelz_1.ChannelzCallTracker(), messagesSent: 0, messagesReceived: 0, keepAlivesSent: 0, lastMessageSentTimestamp: null, lastMessageReceivedTimestamp: null, }; (_d = this.http2Servers.get(http2Server)) === null || _d === void 0 ? void 0 : _d.sessions.add(session); this.sessions.set(session, channelzSessionInfo); const clientAddress = `${session.socket.remoteAddress}:${session.socket.remotePort}`; this.channelzTrace.addTrace('CT_INFO', 'Connection established by client ' + clientAddress); this.trace('Connection established by client ' + clientAddress); this.sessionChildrenTracker.refChild(channelzRef); let connectionAgeTimer = null; let connectionAgeGraceTimer = null; let keepaliveTimeout = null; let sessionClosedByServer = false; const idleTimeoutObj = this.enableIdleTimeout(session); if (this.maxConnectionAgeMs !== UNLIMITED_CONNECTION_AGE_MS) { // Apply a random jitter within a +/-10% range const jitterMagnitude = this.maxConnectionAgeMs / 10; const jitter = Math.random() * jitterMagnitude * 2 - jitterMagnitude; connectionAgeTimer = setTimeout(() => { var _b; sessionClosedByServer = true; this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by max connection age from ' + clientAddress); try { session.goaway(http2.constants.NGHTTP2_NO_ERROR, ~(1 << 31), kMaxAge); } catch (e) { // The goaway can't be sent because the session is already closed session.destroy(); return; } session.close(); /* Allow a grace period after sending the GOAWAY before forcibly * closing the connection. */ if (this.maxConnectionAgeGraceMs !== UNLIMITED_CONNECTION_AGE_MS) { connectionAgeGraceTimer = setTimeout(() => { session.destroy(); }, this.maxConnectionAgeGraceMs); (_b = connectionAgeGraceTimer.unref) === null || _b === void 0 ? void 0 : _b.call(connectionAgeGraceTimer); } }, this.maxConnectionAgeMs + jitter); (_e = connectionAgeTimer.unref) === null || _e === void 0 ? void 0 : _e.call(connectionAgeTimer); } const clearKeepaliveTimeout = () => { if (keepaliveTimeout) { clearTimeout(keepaliveTimeout); keepaliveTimeout = null; } }; const canSendPing = () => { return (!session.destroyed && this.keepaliveTimeMs < KEEPALIVE_MAX_TIME_MS && this.keepaliveTimeMs > 0); }; /* eslint-disable-next-line prefer-const */ let sendPing; // hoisted for use in maybeStartKeepalivePingTimer const maybeStartKeepalivePingTimer = () => { var _b; if (!canSendPing()) { return; } this.keepaliveTrace('Starting keepalive timer for ' + this.keepaliveTimeMs + 'ms'); keepaliveTimeout = setTimeout(() => { clearKeepaliveTimeout(); sendPing(); }, this.keepaliveTimeMs); (_b = keepaliveTimeout.unref) === null || _b === void 0 ? void 0 : _b.call(keepaliveTimeout); }; sendPing = () => { var _b; if (!canSendPing()) { return; } this.keepaliveTrace('Sending ping with timeout ' + this.keepaliveTimeoutMs + 'ms'); let pingSendError = ''; try { const pingSentSuccessfully = session.ping((err, duration, payload) => { clearKeepaliveTimeout(); if (err) { this.keepaliveTrace('Ping failed with error: ' + err.message); this.channelzTrace.addTrace('CT_INFO', 'Connection dropped due to error of a ping frame ' + err.message + ' return in ' + duration); sessionClosedByServer = true; session.close(); } else { this.keepaliveTrace('Received ping response'); maybeStartKeepalivePingTimer(); } }); if (!pingSentSuccessfully) { pingSendError = 'Ping returned false'; } } catch (e) { // grpc/grpc-node#2139 pingSendError = (e instanceof Error ? e.message : '') || 'Unknown error'; } if (pingSendError) { this.keepaliveTrace('Ping send failed: ' + pingSendError); this.channelzTrace.addTrace('CT_INFO', 'Connection dropped due to ping send error: ' + pingSendError); sessionClosedByServer = true; session.close(); return; } channelzSessionInfo.keepAlivesSent += 1; keepaliveTimeout = setTimeout(() => { clearKeepaliveTimeout(); this.keepaliveTrace('Ping timeout passed without response'); this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by keepalive timeout from ' + clientAddress); sessionClosedByServer = true; session.close(); }, this.keepaliveTimeoutMs); (_b = keepaliveTimeout.unref) === null || _b === void 0 ? void 0 : _b.call(keepaliveTimeout); }; maybeStartKeepalivePingTimer(); session.on('close', () => { var _b; if (!sessionClosedByServer) { this.channelzTrace.addTrace('CT_INFO', 'Connection dropped by client ' + clientAddress); } this.sessionChildrenTracker.unrefChild(channelzRef); (0, channelz_1.unregisterChannelzRef)(channelzRef); if (connectionAgeTimer) { clearTimeout(connectionAgeTimer); } if (connectionAgeGraceTimer) { clearTimeout(connectionAgeGraceTimer); } clearKeepaliveTimeout(); if (idleTimeoutObj !== null) { clearTimeout(idleTimeoutObj.timeout); this.sessionIdleTimeouts.delete(session); } (_b = this.http2Servers.get(http2Server)) === null || _b === void 0 ? void 0 : _b.sessions.delete(session); this.sessions.delete(session); }); }; } enableIdleTimeout(session) { var _b, _c; if (this.sessionIdleTimeout >= MAX_CONNECTION_IDLE_MS) { return null; } const idleTimeoutObj = { activeStreams: 0, lastIdle: Date.now(), onClose: this.onStreamClose.bind(this, session), timeout: setTimeout(this.onIdleTimeout, this.sessionIdleTimeout, this, session), }; (_c = (_b = idleTimeoutObj.timeout).unref) === null || _c === void 0 ? void 0 : _c.call(_b); this.sessionIdleTimeouts.set(session, idleTimeoutObj); const { socket } = session; this.trace('Enable idle timeout for ' + socket.remoteAddress + ':' + socket.remotePort); return idleTimeoutObj; } onIdleTimeout(ctx, session) { const { socket } = session; const sessionInfo = ctx.sessionIdleTimeouts.get(session); // if it is called while we have activeStreams - timer will not be rescheduled // until last active stream is closed, then it will call .refresh() on the timer // important part is to not clearTimeout(timer) or it becomes unusable // for future refreshes if (sessionInfo !== undefined && sessionInfo.activeStreams === 0) { if (Date.now() - sessionInfo.lastIdle >= ctx.sessionIdleTimeout) { ctx.trace('Session idle timeout triggered for ' + (socket === null || socket === void 0 ? void 0 : socket.remoteAddress) + ':' + (socket === null || socket === void 0 ? void 0 : socket.remotePort) + ' last idle at ' + sessionInfo.lastIdle); ctx.closeSession(session); } else { sessionInfo.timeout.refresh(); } } } onStreamOpened(stream) { const session = stream.session; const idleTimeoutObj = this.sessionIdleTimeouts.get(session); if (idleTimeoutObj) { idleTimeoutObj.activeStreams += 1; stream.once('close', idleTimeoutObj.onClose); } } onStreamClose(session) { var _b, _c; const idleTimeoutObj = this.sessionIdleTimeouts.get(session); if (idleTimeoutObj) { idleTimeoutObj.activeStreams -= 1; if (idleTimeoutObj.activeStreams === 0) { idleTimeoutObj.lastIdle = Date.now(); idleTimeoutObj.timeout.refresh(); this.trace('Session onStreamClose' + ((_b = session.socket) === null || _b === void 0 ? void 0 : _b.remoteAddress) + ':' + ((_c = session.socket) === null || _c === void 0 ? void 0 : _c.remotePort) + ' at ' + idleTimeoutObj.lastIdle); } } } }, (() => { const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(null) : void 0; _start_decorators = [deprecate('Calling start() is no longer necessary. It can be safely omitted.')]; __esDecorate(_a, null, _start_decorators, { kind: "method", name: "start", static: false, private: false, access: { has: obj => "start" in obj, get: obj => obj.start }, metadata: _metadata }, null, _instanceExtraInitializers); if (_metadata) Object.defineProperty(_a, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata }); })(), _a; })(); exports.Server = Server; async function handleUnary(call, handler) { let stream; function respond(err, value, trailer, flags) { if (err) { call.sendStatus((0, server_call_1.serverErrorToStatus)(err, trailer)); return; } call.sendMessage(value, () => { call.sendStatus({ code: constants_1.Status.OK, details: 'OK', metadata: trailer !== null && trailer !== void 0 ? trailer : null, }); }); } let requestMetadata; let requestMessage = null; call.start({ onReceiveMetadata(metadata) { requestMetadata = metadata; call.startRead(); }, onReceiveMessage(message) { if (requestMessage) { call.sendStatus({ code: constants_1.Status.UNIMPLEMENTED, details: `Received a second request message for server streaming method ${handler.path}`, metadata: null, }); return; } requestMessage = message; call.startRead(); }, onReceiveHalfClose() { if (!requestMessage) { call.sendStatus({ code: constants_1.Status.UNIMPLEMENTED, details: `Received no request message for server streaming method ${handler.path}`, metadata: null, }); return; } stream = new server_call_1.ServerWritableStreamImpl(handler.path, call, requestMetadata, requestMessage); try { handler.func(stream, respond); } catch (err) { call.sendStatus({ code: constants_1.Status.UNKNOWN, details: `Server method handler threw error ${err.message}`, metadata: null, }); } }, onCancel() { if (stream) { stream.cancelled = true; stream.emit('cancelled', 'cancelled'); } }, }); } function handleClientStreaming(call, handler) { let stream; function respond(err, value, trailer, flags) { if (err) { call.sendStatus((0, server_call_1.serverErrorToStatus)(err, trailer)); return; } call.sendMessage(value, () => { call.sendStatus({ code: constants_1.Status.OK, details: 'OK', metadata: trailer !== null && trailer !== void 0 ? trailer : null, }); }); } call.start({ onReceiveMetadata(metadata) { stream = new server_call_1.ServerDuplexStreamImpl(handler.path, call, metadata); try { handler.func(stream, respond); } catch (err) { call.sendStatus({ code: constants_1.Status.UNKNOWN, details: `Server method handler threw error ${err.message}`, metadata: null, }); } }, onReceiveMessage(message) { stream.push(message); }, onReceiveHalfClose() { stream.push(null); }, onCancel() { if (stream) { stream.cancelled = true; stream.emit('cancelled', 'cancelled'); stream.destroy(); } }, }); } function handleServerStreaming(call, handler) { let stream; let requestMetadata; let requestMessage = null; call.start({ onReceiveMetadata(metadata) { requestMetadata = metadata; call.startRead(); }, onReceiveMessage(message) { if (requestMessage) { call.sendStatus({ code: constants_1.Status.UNIMPLEMENTED, details: `Received a second request message for server streaming method ${handler.path}`, metadata: null, }); return; } requestMessage = message; call.startRead(); }, onReceiveHalfClose() { if (!requestMessage) { call.sendStatus({ code: constants_1.Status.UNIMPLEMENTED, details: `Received no request message for server streaming method ${handler.path}`, metadata: null, }); return; } stream = new server_call_1.ServerWritableStreamImpl(handler.path, call, requestMetadata, requestMessage); try { handler.func(stream); } catch (err) { call.sendStatus({ code: constants_1.Status.UNKNOWN, details: `Server method handler threw error ${err.message}`, metadata: null, }); } }, onCancel() { if (stream) { stream.cancelled = true; stream.emit('cancelled', 'cancelled'); stream.destroy(); } }, }); } function handleBidiStreaming(call, handler) { let stream; call.start({ onReceiveMetadata(metadata) { stream = new server_call_1.ServerDuplexStreamImpl(handler.path, call, metadata); try { handler.func(stream); } catch (err) { call.sendStatus({ code: constants_1.Status.UNKNOWN, details: `Server method handler threw error ${err.message}`, metadata: null, }); } }, onReceiveMessage(message) { stream.push(message); }, onReceiveHalfClose() { stream.push(null); }, onCancel() { if (stream) { stream.cancelled = true; stream.emit('cancelled', 'cancelled'); stream.destroy(); } }, }); } //# sourceMappingURL=server.js.map