mirror of https://github.com/grpc/grpc-node.git
grpc-js: define Server API contract
This commit defines the Server API contract, and implements the Server functionality, minus the actual handling of requests.
This commit is contained in:
parent
167625719d
commit
62e7f0c85a
|
@ -15,6 +15,7 @@
|
|||
"types": "build/src/index.d.ts",
|
||||
"license": "Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@grpc/proto-loader": "^0.4.0",
|
||||
"@types/lodash": "^4.14.108",
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/node": "^11.13.2",
|
||||
|
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* 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 {EventEmitter} from 'events';
|
||||
import {Duplex, Readable, Writable} from 'stream';
|
||||
import {ServiceError} from './call';
|
||||
import {Deserialize, Serialize} from './make-client';
|
||||
import {Metadata} from './metadata';
|
||||
|
||||
|
||||
export class ServerUnaryCall<RequestType> extends EventEmitter {
|
||||
cancelled: boolean;
|
||||
request: RequestType|null;
|
||||
|
||||
constructor(private call: ServerCall, public metadata: Metadata) {
|
||||
super();
|
||||
this.cancelled = false;
|
||||
this.request = null; // TODO(cjihrig): Read the unary request here.
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ServerReadableStream<RequestType> extends Readable {
|
||||
cancelled: boolean;
|
||||
|
||||
constructor(
|
||||
private call: ServerCall, public metadata: Metadata,
|
||||
private deserialize: Deserialize<RequestType>) {
|
||||
super();
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ServerWritableStream<RequestType, ResponseType> extends Writable {
|
||||
cancelled: boolean;
|
||||
request: RequestType|null;
|
||||
|
||||
constructor(
|
||||
private call: ServerCall, public metadata: Metadata,
|
||||
private serialize: Serialize<ResponseType>) {
|
||||
super();
|
||||
this.cancelled = false;
|
||||
this.request = null; // TODO(cjihrig): Read the unary request here.
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class ServerDuplexStream<RequestType, ResponseType> extends Duplex {
|
||||
cancelled: boolean;
|
||||
|
||||
constructor(
|
||||
private call: ServerCall, public metadata: Metadata,
|
||||
private serialize: Serialize<ResponseType>,
|
||||
private deserialize: Deserialize<RequestType>) {
|
||||
super();
|
||||
this.cancelled = false;
|
||||
}
|
||||
|
||||
getPeer(): string {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
|
||||
sendMetadata(responseMetadata: Metadata): void {
|
||||
throw new Error('not implemented yet');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Internal class that wraps the HTTP2 request.
|
||||
export class ServerCall {}
|
||||
|
||||
|
||||
// Unary response callback signature.
|
||||
export type sendUnaryData<ResponseType> =
|
||||
(error: ServiceError|null, value: ResponseType|null, trailer?: Metadata,
|
||||
flags?: number) => void;
|
||||
|
||||
// User provided handler for unary calls.
|
||||
export type handleUnaryCall<RequestType, ResponseType> =
|
||||
(call: ServerUnaryCall<RequestType>,
|
||||
callback: sendUnaryData<ResponseType>) => void;
|
||||
|
||||
// User provided handler for client streaming calls.
|
||||
export type handleClientStreamingCall<RequestType, ResponseType> =
|
||||
(call: ServerReadableStream<RequestType>,
|
||||
callback: sendUnaryData<ResponseType>) => void;
|
||||
|
||||
// User provided handler for server streaming calls.
|
||||
export type handleServerStreamingCall<RequestType, ResponseType> =
|
||||
(call: ServerWritableStream<RequestType, ResponseType>) => void;
|
||||
|
||||
// User provided handler for bidirectional streaming calls.
|
||||
export type handleBidiStreamingCall<RequestType, ResponseType> =
|
||||
(call: ServerDuplexStream<RequestType, ResponseType>) => void;
|
||||
|
||||
export type HandleCall<RequestType, ResponseType> =
|
||||
handleUnaryCall<RequestType, ResponseType>|
|
||||
handleClientStreamingCall<RequestType, ResponseType>|
|
||||
handleServerStreamingCall<RequestType, ResponseType>|
|
||||
handleBidiStreamingCall<RequestType, ResponseType>;
|
||||
|
||||
export type Handler<RequestType, ResponseType> = {
|
||||
func: HandleCall<RequestType, ResponseType>;
|
||||
serialize: Serialize<ResponseType>;
|
||||
deserialize: Deserialize<RequestType>;
|
||||
type: HandlerType;
|
||||
};
|
||||
|
||||
export type HandlerType = 'bidi'|'clientStream'|'serverStream'|'unary';
|
|
@ -0,0 +1,230 @@
|
|||
/*
|
||||
* 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 http2 from 'http2';
|
||||
import {AddressInfo, ListenOptions} from 'net';
|
||||
import {URL} from 'url';
|
||||
|
||||
import {ServiceError} from './call';
|
||||
import {Status} from './constants';
|
||||
import {Deserialize, Serialize, ServiceDefinition} from './make-client';
|
||||
import {HandleCall, Handler, HandlerType, sendUnaryData, ServerDuplexStream, ServerReadableStream, ServerUnaryCall, ServerWritableStream} from './server-call';
|
||||
import {ServerCredentials} from './server-credentials';
|
||||
|
||||
function noop(): void {}
|
||||
|
||||
type PartialServiceError = Partial<ServiceError>;
|
||||
const unimplementedStatusResponse: PartialServiceError = {
|
||||
code: Status.UNIMPLEMENTED,
|
||||
details: 'The server does not implement this method',
|
||||
};
|
||||
|
||||
// tslint:disable:no-any
|
||||
type UntypedHandleCall = HandleCall<any, any>;
|
||||
type UntypedHandler = Handler<any, any>;
|
||||
type UntypedServiceImplementation = {
|
||||
[name: string]: UntypedHandleCall
|
||||
};
|
||||
|
||||
const defaultHandler = {
|
||||
unary(call: ServerUnaryCall<any>, callback: sendUnaryData<any>): void {
|
||||
callback(unimplementedStatusResponse as ServiceError, null);
|
||||
},
|
||||
clientStream(call: ServerReadableStream<any>, callback: sendUnaryData<any>):
|
||||
void {
|
||||
callback(unimplementedStatusResponse as ServiceError, null);
|
||||
},
|
||||
serverStream(call: ServerWritableStream<any, any>): void {
|
||||
call.emit('error', unimplementedStatusResponse);
|
||||
},
|
||||
bidi(call: ServerDuplexStream<any, any>): void {
|
||||
call.emit('error', unimplementedStatusResponse);
|
||||
}
|
||||
};
|
||||
// tslint:enable:no-any
|
||||
|
||||
export class Server {
|
||||
private http2Server: http2.Http2Server|http2.Http2SecureServer|null = null;
|
||||
private handlers: Map<string, UntypedHandler> =
|
||||
new Map<string, UntypedHandler>();
|
||||
private started = false;
|
||||
|
||||
constructor(options?: object) {}
|
||||
|
||||
addProtoService(): void {
|
||||
throw new Error('Not implemented. Use addService() instead');
|
||||
}
|
||||
|
||||
addService(service: ServiceDefinition, implementation: object): void {
|
||||
if (this.started === true) {
|
||||
throw new Error('Can\'t add a service to a started server.');
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
const implMap: UntypedServiceImplementation =
|
||||
implementation as UntypedServiceImplementation;
|
||||
|
||||
serviceKeys.forEach((name) => {
|
||||
const attrs = service[name];
|
||||
let methodType: HandlerType;
|
||||
|
||||
if (attrs.requestStream) {
|
||||
if (attrs.responseStream) {
|
||||
methodType = 'bidi';
|
||||
} else {
|
||||
methodType = 'clientStream';
|
||||
}
|
||||
} else {
|
||||
if (attrs.responseStream) {
|
||||
methodType = 'serverStream';
|
||||
} else {
|
||||
methodType = 'unary';
|
||||
}
|
||||
}
|
||||
|
||||
let implFn = implMap[name];
|
||||
let impl;
|
||||
|
||||
if (implFn === undefined && typeof attrs.originalName === 'string') {
|
||||
implFn = implMap[attrs.originalName];
|
||||
}
|
||||
|
||||
if (implFn !== undefined) {
|
||||
impl = implFn.bind(implementation);
|
||||
} else {
|
||||
impl = defaultHandler[methodType];
|
||||
}
|
||||
|
||||
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.`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bind(port: string, creds: ServerCredentials): void {
|
||||
throw new Error('Not implemented. Use bindAsync() instead');
|
||||
}
|
||||
|
||||
bindAsync(
|
||||
port: string, creds: ServerCredentials,
|
||||
callback: (error: Error|null, port: number) => void): void {
|
||||
if (this.started === true) {
|
||||
throw new Error('server is already started');
|
||||
}
|
||||
|
||||
if (typeof port !== 'string') {
|
||||
throw new TypeError('port must be a string');
|
||||
}
|
||||
|
||||
if (creds === null || typeof creds !== 'object') {
|
||||
throw new TypeError('creds must be an object');
|
||||
}
|
||||
|
||||
if (typeof callback !== 'function') {
|
||||
throw new TypeError('callback must be a function');
|
||||
}
|
||||
|
||||
const url = new URL(`http://${port}`);
|
||||
const options: ListenOptions = {host: url.hostname, port: +url.port};
|
||||
|
||||
if (creds._isSecure()) {
|
||||
this.http2Server = http2.createSecureServer(
|
||||
creds._getSettings() as http2.SecureServerOptions);
|
||||
} else {
|
||||
this.http2Server = http2.createServer();
|
||||
}
|
||||
|
||||
// TODO(cjihrig): Set up the handlers, to allow requests to be processed.
|
||||
|
||||
function onError(err: Error): void {
|
||||
callback(err, -1);
|
||||
}
|
||||
|
||||
this.http2Server.once('error', onError);
|
||||
|
||||
this.http2Server.listen(options, () => {
|
||||
const server =
|
||||
this.http2Server as http2.Http2Server | http2.Http2SecureServer;
|
||||
const port = (server.address() as AddressInfo).port;
|
||||
|
||||
server.removeListener('error', onError);
|
||||
callback(null, port);
|
||||
});
|
||||
}
|
||||
|
||||
forceShutdown(): void {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
|
||||
register<RequestType, ResponseType>(
|
||||
name: string, handler: HandleCall<RequestType, ResponseType>,
|
||||
serialize: Serialize<ResponseType>, deserialize: Deserialize<RequestType>,
|
||||
type: string): boolean {
|
||||
if (this.handlers.has(name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.handlers.set(
|
||||
name,
|
||||
{func: handler, serialize, deserialize, type: type as HandlerType});
|
||||
return true;
|
||||
}
|
||||
|
||||
start(): void {
|
||||
if (this.http2Server === null || this.http2Server.listening !== true) {
|
||||
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: (error?: Error) => void): void {
|
||||
callback = typeof callback === 'function' ? callback : noop;
|
||||
|
||||
if (this.http2Server === null) {
|
||||
callback(new Error('server is not running'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.http2Server.close((err?: Error) => {
|
||||
this.started = false;
|
||||
callback(err);
|
||||
});
|
||||
}
|
||||
|
||||
addHttp2Port(): void {
|
||||
throw new Error('Not yet implemented');
|
||||
}
|
||||
}
|
|
@ -15,8 +15,19 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import * as loader from '@grpc/proto-loader';
|
||||
import * as assert from 'assert';
|
||||
|
||||
import {GrpcObject, loadPackageDefinition} from '../src/make-client';
|
||||
|
||||
const protoLoaderOptions = {
|
||||
keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
};
|
||||
|
||||
export function mockFunction(): never {
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
@ -100,3 +111,8 @@ export namespace assert2 {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function loadProtoFile(file: string): GrpcObject {
|
||||
const packageDefinition = loader.loadSync(file, protoLoaderOptions);
|
||||
return loadPackageDefinition(packageDefinition);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package math;
|
||||
|
||||
message DivArgs {
|
||||
int64 dividend = 1;
|
||||
int64 divisor = 2;
|
||||
}
|
||||
|
||||
message DivReply {
|
||||
int64 quotient = 1;
|
||||
int64 remainder = 2;
|
||||
}
|
||||
|
||||
message FibArgs {
|
||||
int64 limit = 1;
|
||||
}
|
||||
|
||||
message Num {
|
||||
int64 num = 1;
|
||||
}
|
||||
|
||||
message FibReply {
|
||||
int64 count = 1;
|
||||
}
|
||||
|
||||
service Math {
|
||||
// Div divides DivArgs.dividend by DivArgs.divisor and returns the quotient
|
||||
// and remainder.
|
||||
rpc Div (DivArgs) returns (DivReply) {
|
||||
}
|
||||
|
||||
// DivMany accepts an arbitrary number of division args from the client stream
|
||||
// and sends back the results in the reply stream. The stream continues until
|
||||
// the client closes its end; the server does the same after sending all the
|
||||
// replies. The stream ends immediately if either end aborts.
|
||||
rpc DivMany (stream DivArgs) returns (stream DivReply) {
|
||||
}
|
||||
|
||||
// Fib generates numbers in the Fibonacci sequence. If FibArgs.limit > 0, Fib
|
||||
// generates up to limit numbers; otherwise it continues until the call is
|
||||
// canceled. Unlike Fib above, Fib has no final FibReply.
|
||||
rpc Fib (FibArgs) returns (stream Num) {
|
||||
}
|
||||
|
||||
// Sum sums a stream of numbers, returning the final result once the stream
|
||||
// is closed.
|
||||
rpc Sum (stream Num) returns (Num) {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,231 @@
|
|||
/*
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
// Allow `any` data type for testing runtime type checking.
|
||||
// tslint:disable no-any
|
||||
import * as assert from 'assert';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import {ServerCredentials} from '../src';
|
||||
import {Server} from '../src/server';
|
||||
|
||||
import {loadProtoFile} from './common';
|
||||
|
||||
const ca = fs.readFileSync(path.join(__dirname, 'fixtures', 'ca.pem'));
|
||||
const key = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.key'));
|
||||
const cert = fs.readFileSync(path.join(__dirname, 'fixtures', 'server1.pem'));
|
||||
function noop(): void {}
|
||||
|
||||
|
||||
describe('Server', () => {
|
||||
describe('constructor', () => {
|
||||
it('should work with no arguments', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
new Server(); // tslint:disable-line:no-unused-expression
|
||||
});
|
||||
});
|
||||
|
||||
it('should work with an empty object argument', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
new Server({}); // tslint:disable-line:no-unused-expression
|
||||
});
|
||||
});
|
||||
|
||||
it('should be an instance of Server', () => {
|
||||
const server = new Server();
|
||||
|
||||
assert(server instanceof Server);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bindAsync', () => {
|
||||
it('binds with insecure credentials', (done) => {
|
||||
const server = new Server();
|
||||
|
||||
server.bindAsync(
|
||||
'localhost:0', ServerCredentials.createInsecure(), (err, port) => {
|
||||
assert.ifError(err);
|
||||
assert(typeof port === 'number' && port > 0);
|
||||
server.tryShutdown(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('binds with secure credentials', (done) => {
|
||||
const server = new Server();
|
||||
const creds = ServerCredentials.createSsl(
|
||||
ca, [{private_key: key, cert_chain: cert}], true);
|
||||
|
||||
server.bindAsync('localhost:0', creds, (err, port) => {
|
||||
assert.ifError(err);
|
||||
assert(typeof port === 'number' && port > 0);
|
||||
server.tryShutdown(done);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if bind is called after the server is started', () => {
|
||||
const server = new Server();
|
||||
|
||||
server.bindAsync(
|
||||
'localhost:0', ServerCredentials.createInsecure(), (err, port) => {
|
||||
assert.ifError(err);
|
||||
server.start();
|
||||
assert.throws(() => {
|
||||
server.bindAsync(
|
||||
'localhost:0', ServerCredentials.createInsecure(), noop);
|
||||
}, /server is already started/);
|
||||
});
|
||||
});
|
||||
|
||||
it('throws on invalid inputs', () => {
|
||||
const server = new Server();
|
||||
|
||||
assert.throws(() => {
|
||||
server.bindAsync(null as any, ServerCredentials.createInsecure(), noop);
|
||||
}, /port must be a string/);
|
||||
|
||||
assert.throws(() => {
|
||||
server.bindAsync('localhost:0', null as any, noop);
|
||||
}, /creds must be an object/);
|
||||
|
||||
assert.throws(() => {
|
||||
server.bindAsync(
|
||||
'localhost:0', ServerCredentials.createInsecure(), null as any);
|
||||
}, /callback must be a function/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('tryShutdown', () => {
|
||||
it('calls back with an error if the server is not running', (done) => {
|
||||
const server = new Server();
|
||||
|
||||
server.tryShutdown((err) => {
|
||||
assert(err !== undefined && err.message === 'server is not running');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('start', () => {
|
||||
let server: Server;
|
||||
|
||||
beforeEach((done) => {
|
||||
server = new Server();
|
||||
server.bindAsync('localhost:0', ServerCredentials.createInsecure(), done);
|
||||
});
|
||||
|
||||
afterEach((done) => {
|
||||
server.tryShutdown(done);
|
||||
});
|
||||
|
||||
it('starts without error', () => {
|
||||
assert.doesNotThrow(() => {
|
||||
server.start();
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if started twice', () => {
|
||||
server.start();
|
||||
assert.throws(() => {
|
||||
server.start();
|
||||
}, /server is already started/);
|
||||
});
|
||||
|
||||
it('throws if the server is not bound', () => {
|
||||
const server = new Server();
|
||||
|
||||
assert.throws(() => {
|
||||
server.start();
|
||||
}, /server must be bound in order to start/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addService', () => {
|
||||
const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto');
|
||||
const mathClient = (loadProtoFile(mathProtoFile).math as any).Math;
|
||||
const mathServiceAttrs = mathClient.service;
|
||||
const dummyImpls = {div() {}, divMany() {}, fib() {}, sum() {}};
|
||||
const altDummyImpls = {Div() {}, DivMany() {}, Fib() {}, Sum() {}};
|
||||
|
||||
it('succeeds with a single service', () => {
|
||||
const server = new Server();
|
||||
|
||||
assert.doesNotThrow(() => {
|
||||
server.addService(mathServiceAttrs, dummyImpls);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails to add an empty service', () => {
|
||||
const server = new Server();
|
||||
|
||||
assert.throws(() => {
|
||||
server.addService({}, dummyImpls);
|
||||
}, /Cannot add an empty service to a server/);
|
||||
});
|
||||
|
||||
it('fails with conflicting method names', () => {
|
||||
const server = new Server();
|
||||
|
||||
server.addService(mathServiceAttrs, dummyImpls);
|
||||
assert.throws(() => {
|
||||
server.addService(mathServiceAttrs, dummyImpls);
|
||||
}, /Method handler for .+ already provided/);
|
||||
});
|
||||
|
||||
it('supports method names as originally written', () => {
|
||||
const server = new Server();
|
||||
|
||||
assert.doesNotThrow(() => {
|
||||
server.addService(mathServiceAttrs, altDummyImpls);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if the server has been started', (done) => {
|
||||
const server = new Server();
|
||||
|
||||
server.bindAsync(
|
||||
'localhost:0', ServerCredentials.createInsecure(), (err, port) => {
|
||||
assert.ifError(err);
|
||||
server.start();
|
||||
assert.throws(() => {
|
||||
server.addService(mathServiceAttrs, dummyImpls);
|
||||
}, /Can't add a service to a started server\./);
|
||||
server.tryShutdown(done);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('throws when unimplemented methods are called', () => {
|
||||
const server = new Server();
|
||||
|
||||
assert.throws(() => {
|
||||
server.addProtoService();
|
||||
}, /Not implemented. Use addService\(\) instead/);
|
||||
|
||||
assert.throws(() => {
|
||||
server.forceShutdown();
|
||||
}, /Not yet implemented/);
|
||||
|
||||
assert.throws(() => {
|
||||
server.addHttp2Port();
|
||||
}, /Not yet implemented/);
|
||||
|
||||
assert.throws(() => {
|
||||
server.bind('localhost:0', ServerCredentials.createInsecure());
|
||||
}, /Not implemented. Use bindAsync\(\) instead/);
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue