Skip to content

Commit

Permalink
Merge pull request #1614 from hugebdu/master
Browse files Browse the repository at this point in the history
grpc-js: support adding/removing services on started server
  • Loading branch information
murgatroid99 committed Oct 29, 2020
2 parents 1612bf0 + 51ca002 commit d8021d2
Show file tree
Hide file tree
Showing 2 changed files with 139 additions and 8 deletions.
25 changes: 20 additions & 5 deletions packages/grpc-js/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,10 +148,6 @@ export class Server {
service: ServiceDefinition,
implementation: UntypedServiceImplementation
): void {
if (this.started === true) {
throw new Error("Can't add a service to a started server.");
}

if (
service === null ||
typeof service !== 'object' ||
Expand Down Expand Up @@ -212,6 +208,21 @@ export class Server {
});
}

removeService(service: ServiceDefinition): void {
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: string, creds: ServerCredentials): void {
throw new Error('Not implemented. Use bindAsync() instead');
}
Expand Down Expand Up @@ -462,6 +473,10 @@ export class Server {
return true;
}

unregister(name: string): boolean {
return this.handlers.delete(name);
}

start(): void {
if (
this.http2ServerList.length === 0 ||
Expand Down Expand Up @@ -637,7 +652,7 @@ async function handleUnary<RequestType, ResponseType>(
if (request === undefined || call.cancelled) {
return;
}

const emitter = new ServerUnaryCallImpl<RequestType, ResponseType>(
call,
metadata,
Expand Down
122 changes: 119 additions & 3 deletions packages/grpc-js/test/test-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ describe('Server', () => {
});
});

it('fails if the server has been started', done => {
it('succeeds after server has been started', done => {
const server = new Server();

server.bindAsync(
Expand All @@ -211,15 +211,131 @@ describe('Server', () => {
(err, port) => {
assert.ifError(err);
server.start();
assert.throws(() => {
assert.doesNotThrow(() => {
server.addService(mathServiceAttrs, dummyImpls);
}, /Can't add a service to a started server\./);
});
server.tryShutdown(done);
}
);
});
});

describe('removeService', () => {
let server: Server;
let client: ServiceClient;

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

beforeEach(done => {
server = new Server();
server.addService(mathServiceAttrs, dummyImpls);
server.bindAsync(
'localhost:0',
ServerCredentials.createInsecure(),
(err, port) => {
assert.ifError(err);
client = new mathClient(
`localhost:${port}`,
grpc.credentials.createInsecure()
);
server.start();
done();
}
);
});

afterEach(done => {
client.close();
server.tryShutdown(done);
});

it('succeeds with a single service by removing all method handlers', done => {
server.removeService(mathServiceAttrs);

let methodsVerifiedCount = 0;
const methodsToVerify = Object.keys(mathServiceAttrs);

const assertFailsWithUnimplementedError = (error: ServiceError) => {
assert(error);
assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED);
methodsVerifiedCount++;
if (methodsVerifiedCount === methodsToVerify.length) {
done();
}
};

methodsToVerify.forEach((method) => {
const call = client[method]({}, assertFailsWithUnimplementedError); // for unary
call.on('error', assertFailsWithUnimplementedError); // for streamed
});
});

it('fails for non-object service definition argument', () => {
assert.throws(() => {
server.removeService('upsie' as any)
}, /removeService.*requires object as argument/
);
});
});

describe('unregister', () => {

let server: Server;
let client: ServiceClient;

const mathProtoFile = path.join(__dirname, 'fixtures', 'math.proto');
const mathClient = (loadProtoFile(mathProtoFile).math as any).Math;
const mathServiceAttrs = mathClient.service;

beforeEach(done => {
server = new Server();
server.addService(mathServiceAttrs, {
div(call: ServerUnaryCall<any, any>, callback: sendUnaryData<any>) {
callback(null, {quotient: '42'});
},
});
server.bindAsync(
'localhost:0',
ServerCredentials.createInsecure(),
(err, port) => {
assert.ifError(err);
client = new mathClient(
`localhost:${port}`,
grpc.credentials.createInsecure()
);
server.start();
done();
}
);
});

afterEach(done => {
client.close();
server.tryShutdown(done);
});

it('removes handler by name and returns true', done => {
const name = mathServiceAttrs['Div'].path;
assert.strictEqual(server.unregister(name), true, 'Server#unregister should return true on success');

client.div(
{ divisor: 4, dividend: 3 },
(error: ServiceError, response: any) => {
assert(error);
assert.strictEqual(error.code, grpc.status.UNIMPLEMENTED);
done();
}
);
});

it('returns false for unknown handler', () => {
assert.strictEqual(server.unregister('noOneHere'), false, 'Server#unregister should return false on failure');
});
});

it('throws when unimplemented methods are called', () => {
const server = new Server();

Expand Down

0 comments on commit d8021d2

Please sign in to comment.