|
|
|
@ -1,6 +1,6 @@ |
|
|
|
|
/* |
|
|
|
|
* |
|
|
|
|
* Copyright 2014, Google Inc. |
|
|
|
|
* Copyright 2015, Google Inc. |
|
|
|
|
* All rights reserved. |
|
|
|
|
* |
|
|
|
|
* Redistribution and use in source and binary forms, with or without |
|
|
|
@ -33,80 +33,72 @@ |
|
|
|
|
|
|
|
|
|
var _ = require('underscore'); |
|
|
|
|
|
|
|
|
|
var capitalize = require('underscore.string/capitalize'); |
|
|
|
|
var decapitalize = require('underscore.string/decapitalize'); |
|
|
|
|
|
|
|
|
|
var grpc = require('bindings')('grpc.node'); |
|
|
|
|
|
|
|
|
|
var common = require('./common'); |
|
|
|
|
|
|
|
|
|
var Duplex = require('stream').Duplex; |
|
|
|
|
var stream = require('stream'); |
|
|
|
|
|
|
|
|
|
var Readable = stream.Readable; |
|
|
|
|
var Writable = stream.Writable; |
|
|
|
|
var Duplex = stream.Duplex; |
|
|
|
|
var util = require('util'); |
|
|
|
|
|
|
|
|
|
util.inherits(GrpcServerStream, Duplex); |
|
|
|
|
var EventEmitter = require('events').EventEmitter; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Class for representing a gRPC server side stream as a Node stream. Extends |
|
|
|
|
* from stream.Duplex. |
|
|
|
|
* @constructor |
|
|
|
|
* @param {grpc.Call} call Call object to proxy |
|
|
|
|
* @param {function(*):Buffer=} serialize Serialization function for responses |
|
|
|
|
* @param {function(Buffer):*=} deserialize Deserialization function for |
|
|
|
|
* requests |
|
|
|
|
*/ |
|
|
|
|
function GrpcServerStream(call, serialize, deserialize) { |
|
|
|
|
Duplex.call(this, {objectMode: true}); |
|
|
|
|
if (!serialize) { |
|
|
|
|
serialize = function(value) { |
|
|
|
|
return value; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
if (!deserialize) { |
|
|
|
|
deserialize = function(value) { |
|
|
|
|
return value; |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
this._call = call; |
|
|
|
|
// Indicate that a status has been sent
|
|
|
|
|
var finished = false; |
|
|
|
|
var self = this; |
|
|
|
|
var status = { |
|
|
|
|
'code' : grpc.status.OK, |
|
|
|
|
'details' : 'OK' |
|
|
|
|
}; |
|
|
|
|
var common = require('./common.js'); |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Serialize a response value to a buffer. Always maps null to null. Otherwise |
|
|
|
|
* uses the provided serialize function |
|
|
|
|
* @param {*} value The value to serialize |
|
|
|
|
* @return {Buffer} The serialized value |
|
|
|
|
*/ |
|
|
|
|
this.serialize = function(value) { |
|
|
|
|
if (value === null || value === undefined) { |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
return serialize(value); |
|
|
|
|
function handleError(call, error) { |
|
|
|
|
var error_batch = {}; |
|
|
|
|
error_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { |
|
|
|
|
code: grpc.status.INTERNAL, |
|
|
|
|
details: 'Unknown Error', |
|
|
|
|
metadata: {} |
|
|
|
|
}; |
|
|
|
|
call.startBatch(error_batch, function(){}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Deserialize a request buffer to a value. Always maps null to null. |
|
|
|
|
* Otherwise uses the provided deserialize function. |
|
|
|
|
* @param {Buffer} buffer The buffer to deserialize |
|
|
|
|
* @return {*} The deserialized value |
|
|
|
|
*/ |
|
|
|
|
this.deserialize = function(buffer) { |
|
|
|
|
if (buffer === null) { |
|
|
|
|
return null; |
|
|
|
|
function waitForCancel(call, emitter) { |
|
|
|
|
var cancel_batch = {}; |
|
|
|
|
cancel_batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; |
|
|
|
|
call.startBatch(cancel_batch, function(err, result) { |
|
|
|
|
if (err) { |
|
|
|
|
emitter.emit('error', err); |
|
|
|
|
} |
|
|
|
|
return deserialize(buffer); |
|
|
|
|
if (result.cancelled) { |
|
|
|
|
emitter.cancelled = true; |
|
|
|
|
emitter.emit('cancelled'); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function sendUnaryResponse(call, value, serialize) { |
|
|
|
|
var end_batch = {}; |
|
|
|
|
end_batch[grpc.opType.SEND_MESSAGE] = serialize(value); |
|
|
|
|
end_batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { |
|
|
|
|
code: grpc.status.OK, |
|
|
|
|
details: 'OK', |
|
|
|
|
metadata: {} |
|
|
|
|
}; |
|
|
|
|
call.startBatch(end_batch, function (){}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Send the pending status |
|
|
|
|
*/ |
|
|
|
|
function setUpWritable(stream, serialize) { |
|
|
|
|
stream.finished = false; |
|
|
|
|
stream.status = { |
|
|
|
|
'code' : grpc.status.OK, |
|
|
|
|
'details' : 'OK' |
|
|
|
|
}; |
|
|
|
|
stream.serialize = common.wrapIgnoreNull(serialize); |
|
|
|
|
function sendStatus() { |
|
|
|
|
call.startWriteStatus(status.code, status.details, function() { |
|
|
|
|
}); |
|
|
|
|
finished = true; |
|
|
|
|
var batch = {}; |
|
|
|
|
batch[grpc.opType.SEND_STATUS_FROM_SERVER] = stream.status; |
|
|
|
|
stream.call.startBatch(batch, function(){}); |
|
|
|
|
} |
|
|
|
|
this.on('finish', sendStatus); |
|
|
|
|
stream.on('finish', sendStatus); |
|
|
|
|
/** |
|
|
|
|
* Set the pending status to a given error status. If the error does not have |
|
|
|
|
* code or details properties, the code will be set to grpc.status.INTERNAL |
|
|
|
@ -123,7 +115,7 @@ function GrpcServerStream(call, serialize, deserialize) { |
|
|
|
|
details = err.details; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
status = {'code': code, 'details': details}; |
|
|
|
|
stream.status = {'code': code, 'details': details}; |
|
|
|
|
} |
|
|
|
|
/** |
|
|
|
|
* Terminate the call. This includes indicating that reads are done, draining |
|
|
|
@ -133,69 +125,196 @@ function GrpcServerStream(call, serialize, deserialize) { |
|
|
|
|
*/ |
|
|
|
|
function terminateCall(err) { |
|
|
|
|
// Drain readable data
|
|
|
|
|
this.on('data', function() {}); |
|
|
|
|
setStatus(err); |
|
|
|
|
this.end(); |
|
|
|
|
stream.end(); |
|
|
|
|
} |
|
|
|
|
this.on('error', terminateCall); |
|
|
|
|
// Indicates that a read is pending
|
|
|
|
|
var reading = false; |
|
|
|
|
stream.on('error', terminateCall); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function setUpReadable(stream, deserialize) { |
|
|
|
|
stream.deserialize = common.wrapIgnoreNull(deserialize); |
|
|
|
|
stream.finished = false; |
|
|
|
|
stream.reading = false; |
|
|
|
|
|
|
|
|
|
stream.terminate = function() { |
|
|
|
|
stream.finished = true; |
|
|
|
|
stream.on('data', function() {}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
stream.on('cancelled', function() { |
|
|
|
|
stream.terminate(); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
util.inherits(ServerWritableStream, Writable); |
|
|
|
|
|
|
|
|
|
function ServerWritableStream(call, serialize) { |
|
|
|
|
Writable.call(this, {objectMode: true}); |
|
|
|
|
this.call = call; |
|
|
|
|
|
|
|
|
|
this.finished = false; |
|
|
|
|
setUpWritable(this, serialize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Start writing a chunk of data. This is an implementation of a method required |
|
|
|
|
* for implementing stream.Writable. |
|
|
|
|
* @param {Buffer} chunk The chunk of data to write |
|
|
|
|
* @param {string} encoding Ignored |
|
|
|
|
* @param {function(Error=)} callback Callback to indicate that the write is |
|
|
|
|
* complete |
|
|
|
|
*/ |
|
|
|
|
function _write(chunk, encoding, callback) { |
|
|
|
|
var batch = {}; |
|
|
|
|
batch[grpc.opType.SEND_MESSAGE] = this.serialize(chunk); |
|
|
|
|
this.call.startBatch(batch, function(err, value) { |
|
|
|
|
if (err) { |
|
|
|
|
this.emit('error', err); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
callback(); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ServerWritableStream.prototype._write = _write; |
|
|
|
|
|
|
|
|
|
util.inherits(ServerReadableStream, Readable); |
|
|
|
|
|
|
|
|
|
function ServerReadableStream(call, deserialize) { |
|
|
|
|
Readable.call(this, {objectMode: true}); |
|
|
|
|
this.call = call; |
|
|
|
|
setUpReadable(this, deserialize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Start reading from the gRPC data source. This is an implementation of a |
|
|
|
|
* method required for implementing stream.Readable |
|
|
|
|
* @param {number} size Ignored |
|
|
|
|
*/ |
|
|
|
|
function _read(size) { |
|
|
|
|
var self = this; |
|
|
|
|
/** |
|
|
|
|
* Callback to be called when a READ event is received. Pushes the data onto |
|
|
|
|
* the read queue and starts reading again if applicable |
|
|
|
|
* @param {grpc.Event} event READ event object |
|
|
|
|
*/ |
|
|
|
|
function readCallback(event) { |
|
|
|
|
if (finished) { |
|
|
|
|
function readCallback(err, event) { |
|
|
|
|
if (err) { |
|
|
|
|
self.terminate(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (self.finished) { |
|
|
|
|
self.push(null); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
var data = event.data; |
|
|
|
|
var data = event.read; |
|
|
|
|
if (self.push(self.deserialize(data)) && data != null) { |
|
|
|
|
self._call.startRead(readCallback); |
|
|
|
|
var read_batch = {}; |
|
|
|
|
read_batch[grpc.opType.RECV_MESSAGE] = true; |
|
|
|
|
self.call.startBatch(read_batch, readCallback); |
|
|
|
|
} else { |
|
|
|
|
reading = false; |
|
|
|
|
self.reading = false; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
/** |
|
|
|
|
* Start reading if there is not already a pending read. Reading will |
|
|
|
|
* continue until self.push returns false (indicating reads should slow |
|
|
|
|
* down) or the read data is null (indicating that there is no more data). |
|
|
|
|
*/ |
|
|
|
|
this.startReading = function() { |
|
|
|
|
if (finished) { |
|
|
|
|
self.push(null); |
|
|
|
|
} else { |
|
|
|
|
if (!reading) { |
|
|
|
|
reading = true; |
|
|
|
|
self._call.startRead(readCallback); |
|
|
|
|
if (self.finished) { |
|
|
|
|
self.push(null); |
|
|
|
|
} else { |
|
|
|
|
if (!self.reading) { |
|
|
|
|
self.reading = true; |
|
|
|
|
var batch = {}; |
|
|
|
|
batch[grpc.opType.RECV_MESSAGE] = true; |
|
|
|
|
self.call.startBatch(batch, readCallback); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ServerReadableStream.prototype._read = _read; |
|
|
|
|
|
|
|
|
|
util.inherits(ServerDuplexStream, Duplex); |
|
|
|
|
|
|
|
|
|
function ServerDuplexStream(call, serialize, deserialize) { |
|
|
|
|
Duplex.call(this, {objectMode: true}); |
|
|
|
|
setUpWritable(this, serialize); |
|
|
|
|
setUpReadable(this, deserialize); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
ServerDuplexStream.prototype._read = _read; |
|
|
|
|
ServerDuplexStream.prototype._write = _write; |
|
|
|
|
|
|
|
|
|
function handleUnary(call, handler, metadata) { |
|
|
|
|
var emitter = new EventEmitter(); |
|
|
|
|
emitter.on('error', function(error) { |
|
|
|
|
handleError(call, error); |
|
|
|
|
}); |
|
|
|
|
waitForCancel(call, emitter); |
|
|
|
|
var batch = {}; |
|
|
|
|
batch[grpc.opType.SEND_INITIAL_METADATA] = metadata; |
|
|
|
|
batch[grpc.opType.RECV_MESSAGE] = true; |
|
|
|
|
call.startBatch(batch, function(err, result) { |
|
|
|
|
if (err) { |
|
|
|
|
handleError(call, err); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
emitter.request = handler.deserialize(result.read); |
|
|
|
|
if (emitter.cancelled) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
handler.func(emitter, function sendUnaryData(err, value) { |
|
|
|
|
if (err) { |
|
|
|
|
handleError(call, err); |
|
|
|
|
} |
|
|
|
|
sendUnaryResponse(call, value, handler.serialize); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function handleServerStreaming(call, handler, metadata) { |
|
|
|
|
console.log('Handling server streaming call'); |
|
|
|
|
var stream = new ServerWritableStream(call, handler.serialize); |
|
|
|
|
waitForCancel(call, stream); |
|
|
|
|
var batch = {}; |
|
|
|
|
batch[grpc.opType.SEND_INITIAL_METADATA] = metadata; |
|
|
|
|
batch[grpc.opType.RECV_MESSAGE] = true; |
|
|
|
|
call.startBatch(batch, function(err, result) { |
|
|
|
|
if (err) { |
|
|
|
|
stream.emit('error', err); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
stream.request = result.read; |
|
|
|
|
handler.func(stream); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Start reading from the gRPC data source. This is an implementation of a |
|
|
|
|
* method required for implementing stream.Readable |
|
|
|
|
* @param {number} size Ignored |
|
|
|
|
*/ |
|
|
|
|
GrpcServerStream.prototype._read = function(size) { |
|
|
|
|
this.startReading(); |
|
|
|
|
}; |
|
|
|
|
function handleClientStreaming(call, handler, metadata) { |
|
|
|
|
var stream = new ServerReadableStream(call, handler.deserialize); |
|
|
|
|
waitForCancel(call, stream); |
|
|
|
|
var metadata_batch = {}; |
|
|
|
|
metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata; |
|
|
|
|
call.startBatch(metadata_batch, function() {}); |
|
|
|
|
handler.func(stream, function(err, value) { |
|
|
|
|
stream.terminate(); |
|
|
|
|
if (err) { |
|
|
|
|
handleError(call, err); |
|
|
|
|
} |
|
|
|
|
sendUnaryResponse(call, value, handler.serialize); |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Start writing a chunk of data. This is an implementation of a method required |
|
|
|
|
* for implementing stream.Writable. |
|
|
|
|
* @param {Buffer} chunk The chunk of data to write |
|
|
|
|
* @param {string} encoding Ignored |
|
|
|
|
* @param {function(Error=)} callback Callback to indicate that the write is |
|
|
|
|
* complete |
|
|
|
|
*/ |
|
|
|
|
GrpcServerStream.prototype._write = function(chunk, encoding, callback) { |
|
|
|
|
var self = this; |
|
|
|
|
self._call.startWrite(self.serialize(chunk), function(event) { |
|
|
|
|
callback(); |
|
|
|
|
}, 0); |
|
|
|
|
function handleBidiStreaming(call, handler, metadata) { |
|
|
|
|
var stream = new ServerDuplexStream(call, handler.serialize, |
|
|
|
|
handler.deserialize); |
|
|
|
|
waitForCancel(call, stream); |
|
|
|
|
var metadata_batch = {}; |
|
|
|
|
metadata_batch[grpc.opType.SEND_INITIAL_METADATA] = metadata; |
|
|
|
|
call.startBatch(metadata_batch, function() {}); |
|
|
|
|
handler.func(stream); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var streamHandlers = { |
|
|
|
|
unary: handleUnary, |
|
|
|
|
server_stream: handleServerStreaming, |
|
|
|
|
client_stream: handleClientStreaming, |
|
|
|
|
bidi: handleBidiStreaming |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
@ -218,7 +337,7 @@ function Server(getMetadata, options) { |
|
|
|
|
* Start the server and begin handling requests |
|
|
|
|
* @this Server |
|
|
|
|
*/ |
|
|
|
|
this.start = function() { |
|
|
|
|
this.listen = function() { |
|
|
|
|
console.log('Server starting'); |
|
|
|
|
_.each(handlers, function(handler, handler_name) { |
|
|
|
|
console.log('Serving', handler_name); |
|
|
|
@ -233,48 +352,42 @@ function Server(getMetadata, options) { |
|
|
|
|
* wait for the next request |
|
|
|
|
* @param {grpc.Event} event The event to handle with tag SERVER_RPC_NEW |
|
|
|
|
*/ |
|
|
|
|
function handleNewCall(event) { |
|
|
|
|
var call = event.call; |
|
|
|
|
var data = event.data; |
|
|
|
|
if (data === null) { |
|
|
|
|
function handleNewCall(err, event) { |
|
|
|
|
console.log('Handling new call'); |
|
|
|
|
if (err) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
var details = event['new call']; |
|
|
|
|
var call = details.call; |
|
|
|
|
var method = details.method; |
|
|
|
|
var metadata = details.metadata; |
|
|
|
|
if (method === null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
server.requestCall(handleNewCall); |
|
|
|
|
var handler = undefined; |
|
|
|
|
var deadline = data.absolute_deadline; |
|
|
|
|
var cancelled = false; |
|
|
|
|
call.serverAccept(function(event) { |
|
|
|
|
if (event.data.code === grpc.status.CANCELLED) { |
|
|
|
|
cancelled = true; |
|
|
|
|
if (stream) { |
|
|
|
|
stream.emit('cancelled'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, 0); |
|
|
|
|
if (handlers.hasOwnProperty(data.method)) { |
|
|
|
|
handler = handlers[data.method]; |
|
|
|
|
var deadline = details.deadline; |
|
|
|
|
if (handlers.hasOwnProperty(method)) { |
|
|
|
|
handler = handlers[method]; |
|
|
|
|
console.log(handler); |
|
|
|
|
} else { |
|
|
|
|
call.serverEndInitialMetadata(0); |
|
|
|
|
call.startWriteStatus( |
|
|
|
|
grpc.status.UNIMPLEMENTED, |
|
|
|
|
"This method is not available on this server.", |
|
|
|
|
function() {}); |
|
|
|
|
console.log(handlers); |
|
|
|
|
var batch = {}; |
|
|
|
|
batch[grpc.opType.SEND_INITIAL_METADATA] = {}; |
|
|
|
|
batch[grpc.opType.SEND_STATUS_FROM_SERVER] = { |
|
|
|
|
code: grpc.status.UNIMPLEMENTED, |
|
|
|
|
details: "This method is not available on this server.", |
|
|
|
|
metadata: {} |
|
|
|
|
}; |
|
|
|
|
batch[grpc.opType.RECV_CLOSE_ON_SERVER] = true; |
|
|
|
|
call.startBatch(batch, function() {}); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
var response_metadata = {}; |
|
|
|
|
if (getMetadata) { |
|
|
|
|
call.addMetadata(getMetadata(data.method, data.metadata)); |
|
|
|
|
} |
|
|
|
|
call.serverEndInitialMetadata(0); |
|
|
|
|
var stream = new GrpcServerStream(call, handler.serialize, |
|
|
|
|
handler.deserialize); |
|
|
|
|
Object.defineProperty(stream, 'cancelled', { |
|
|
|
|
get: function() { return cancelled;} |
|
|
|
|
}); |
|
|
|
|
try { |
|
|
|
|
handler.func(stream, data.metadata); |
|
|
|
|
} catch (e) { |
|
|
|
|
stream.emit('error', e); |
|
|
|
|
response_metadata = getMetadata(method, metadata); |
|
|
|
|
} |
|
|
|
|
streamHandlers[handler.type](call, handler, response_metadata); |
|
|
|
|
} |
|
|
|
|
server.requestCall(handleNewCall); |
|
|
|
|
}; |
|
|
|
@ -294,17 +407,20 @@ function Server(getMetadata, options) { |
|
|
|
|
* returns a stream of response values |
|
|
|
|
* @param {function(*):Buffer} serialize Serialization function for responses |
|
|
|
|
* @param {function(Buffer):*} deserialize Deserialization function for requests |
|
|
|
|
* @param {string} type The streaming type of method that this handles |
|
|
|
|
* @return {boolean} True if the handler was set. False if a handler was already |
|
|
|
|
* set for that name. |
|
|
|
|
*/ |
|
|
|
|
Server.prototype.register = function(name, handler, serialize, deserialize) { |
|
|
|
|
Server.prototype.register = function(name, handler, serialize, deserialize, |
|
|
|
|
type) { |
|
|
|
|
if (this.handlers.hasOwnProperty(name)) { |
|
|
|
|
return false; |
|
|
|
|
} |
|
|
|
|
this.handlers[name] = { |
|
|
|
|
func: handler, |
|
|
|
|
serialize: serialize, |
|
|
|
|
deserialize: deserialize |
|
|
|
|
deserialize: deserialize, |
|
|
|
|
type: type |
|
|
|
|
}; |
|
|
|
|
return true; |
|
|
|
|
}; |
|
|
|
@ -324,6 +440,110 @@ Server.prototype.bind = function(port, secure) { |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* See documentation for Server |
|
|
|
|
* Creates a constructor for servers with a service defined by the methods |
|
|
|
|
* object. The methods object has string keys and values of this form: |
|
|
|
|
* {serialize: function, deserialize: function, client_stream: bool, |
|
|
|
|
* server_stream: bool} |
|
|
|
|
* @param {Object} methods Method descriptor for each method the server should |
|
|
|
|
* expose |
|
|
|
|
* @param {string} prefix The prefex to prepend to each method name |
|
|
|
|
* @return {function(Object, Object)} New server constructor |
|
|
|
|
*/ |
|
|
|
|
function makeServerConstructor(services) { |
|
|
|
|
var qual_names = []; |
|
|
|
|
_.each(services, function(service) { |
|
|
|
|
_.each(service.children, function(method) { |
|
|
|
|
var name = common.fullyQualifiedName(method); |
|
|
|
|
if (_.indexOf(qual_names, name) !== -1) { |
|
|
|
|
throw new Error('Method ' + name + ' exposed by more than one service'); |
|
|
|
|
} |
|
|
|
|
qual_names.push(name); |
|
|
|
|
}); |
|
|
|
|
}); |
|
|
|
|
/** |
|
|
|
|
* Create a server with the given handlers for all of the methods. |
|
|
|
|
* @constructor |
|
|
|
|
* @param {Object} service_handlers Map from service names to map from method |
|
|
|
|
* names to handlers |
|
|
|
|
* @param {function(string, Object<string, Array<Buffer>>): |
|
|
|
|
Object<string, Array<Buffer|string>>=} getMetadata Callback that |
|
|
|
|
* gets metatada for a given method |
|
|
|
|
* @param {Object=} options Options to pass to the underlying server |
|
|
|
|
*/ |
|
|
|
|
function SurfaceServer(service_handlers, getMetadata, options) { |
|
|
|
|
var server = new Server(getMetadata, options); |
|
|
|
|
this.inner_server = server; |
|
|
|
|
_.each(services, function(service) { |
|
|
|
|
var service_name = common.fullyQualifiedName(service); |
|
|
|
|
if (service_handlers[service_name] === undefined) { |
|
|
|
|
throw new Error('Handlers for service ' + |
|
|
|
|
service_name + ' not provided.'); |
|
|
|
|
} |
|
|
|
|
var prefix = '/' + common.fullyQualifiedName(service) + '/'; |
|
|
|
|
_.each(service.children, function(method) { |
|
|
|
|
var method_type; |
|
|
|
|
if (method.requestStream) { |
|
|
|
|
if (method.responseStream) { |
|
|
|
|
method_type = 'bidi'; |
|
|
|
|
} else { |
|
|
|
|
method_type = 'client_stream'; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if (method.responseStream) { |
|
|
|
|
method_type = 'server_stream'; |
|
|
|
|
} else { |
|
|
|
|
method_type = 'unary'; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (service_handlers[service_name][decapitalize(method.name)] === |
|
|
|
|
undefined) { |
|
|
|
|
throw new Error('Method handler for ' + |
|
|
|
|
common.fullyQualifiedName(method) + ' not provided.'); |
|
|
|
|
} |
|
|
|
|
var serialize = common.serializeCls( |
|
|
|
|
method.resolvedResponseType.build()); |
|
|
|
|
var deserialize = common.deserializeCls( |
|
|
|
|
method.resolvedRequestType.build()); |
|
|
|
|
server.register( |
|
|
|
|
prefix + capitalize(method.name), |
|
|
|
|
service_handlers[service_name][decapitalize(method.name)], |
|
|
|
|
serialize, deserialize, method_type); |
|
|
|
|
}); |
|
|
|
|
}, this); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Binds the server to the given port, with SSL enabled if secure is specified |
|
|
|
|
* @param {string} port The port that the server should bind on, in the format |
|
|
|
|
* "address:port" |
|
|
|
|
* @param {boolean=} secure Whether the server should open a secure port |
|
|
|
|
* @return {SurfaceServer} this |
|
|
|
|
*/ |
|
|
|
|
SurfaceServer.prototype.bind = function(port, secure) { |
|
|
|
|
return this.inner_server.bind(port, secure); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Starts the server listening on any bound ports |
|
|
|
|
* @return {SurfaceServer} this |
|
|
|
|
*/ |
|
|
|
|
SurfaceServer.prototype.listen = function() { |
|
|
|
|
this.inner_server.listen(); |
|
|
|
|
return this; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Shuts the server down; tells it to stop listening for new requests and to |
|
|
|
|
* kill old requests. |
|
|
|
|
*/ |
|
|
|
|
SurfaceServer.prototype.shutdown = function() { |
|
|
|
|
this.inner_server.shutdown(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return SurfaceServer; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* See documentation for makeServerConstructor |
|
|
|
|
*/ |
|
|
|
|
module.exports = Server; |
|
|
|
|
exports.makeServerConstructor = makeServerConstructor; |
|
|
|
|