barrister.node.js | |
|---|---|
var request = require("request"); | |
| Function to create IDs for requests | var rchars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
function randstr(len) {
var s = "";
var i;
for (i = 0; i < len; i++) {
var rnum = Math.floor(Math.random() * rchars.length);
s += rchars.substring(rnum,rnum+1);
}
return s;
}
var jsonRegex = new RegExp('[\\u007f-\\uffff]', 'g');
var jsonRegexReplace = function(c) {
return '\\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
}; |
| JSON_stringify takes a JSON string and escapes unicode characters Built in JSON.stringify() will return unicode characters that require UTF-8 encoding on the wire. Barrister specifies that all JSON strings be ASCII safe (to minimize surprises). This function will replace unicode characters with their escaped (ASCII-safe) equivalents. If emit_unicode is true then s is returned unchanged | function JSON_stringify(s, emit_unicode) {
if (s) {
var json = JSON.stringify(s);
if (json) {
return emit_unicode ? json : json.replace(jsonRegex, jsonRegexReplace);
}
else {
return json;
}
}
else {
return s;
}
} |
| makeRequest returns a JSON-RPC 2.0 compliant request object, setting 'id', 'method', and 'params'. A random request ID is generated for the request, which is used to correlate requests and responses for batch mode. | function makeRequest(method, params) {
var req = { "jsonrpc": "2.0", "id": randstr(20), "method": method }; |
| JSON-RPC allows params to be omitted if the method takes no params | if (params !== null && params !== undefined) {
req.params = params;
}
return req;
} |
| errResp creates a JSON-RPC 2.0 compliant response object for an error.
| function errResp(id, code, msg, data) {
id = id || null;
return { "jsonrpc": "2.0", "id": id, "error": { "code": code, "message": msg, "data": data } };
} |
| parseResponse creates a JSON-RPC response object from the raw response body
| function parseResponse(req, error, body) {
var resp;
if (error) {
resp = errResp(req.id, -32000, error);
}
else if (body !== undefined && body !== null) { |
| Some clients (like jQuery) want to parse the body eagerly. Assume that if we have an object, we've already been parsed. | if (typeof body === "object") {
resp = body;
}
else {
try {
resp = JSON.parse(body);
}
catch (e) {
resp = errResp(req.id, -32700, "Unable to parse response JSON: " + body);
}
}
}
else {
resp = errResp(req.id, -32603, "Null response body received from server");
}
return resp;
} |
| barrister will only call this if coercion is required (i.e. impls don't need to check for expected type matches) params: expectedType: string. barrister primitive type expected (int, float, string, bool) val: value to coerce returns: if coercion successful, returns the newly coerced value. if coercion failed, returns val | function defaultCoerce(expectedType, val) {
var x, type;
if (val === null || val === undefined) { |
| don't attempt to convert | return val;
}
type = typeof val;
if (type === "boolean" || type === "number") {
if (expectedType === "string") {
return val.toString();
}
}
else if (type === "string") {
if (expectedType === "bool") {
if (val === "true") {
return true;
}
else if (val === "false") {
return false;
}
}
else if (expectedType === "int") {
x = parseInt(val, 10);
if (x.toString() === val && val !== "NaN") {
return x;
}
}
else if (expectedType === "float") {
x = parseFloat(val);
if (x.toString() === val && val !== "NaN") {
return x;
}
}
} |
| fall through | return val;
} |
| The Promise class provides a way to register error/success callbacks for a Barrister function call without inlining them on the call itself. To use this mechanism, simply omit the callback parameter when making a function call. For example:
The Promise constructor is called internally -- you don't create these yourself. | function Promise() {
var me = this;
this.errCallback = null;
this.resultCallback = null;
this.callbackInvoked = false;
this.respReceived = false;
|
| This callback function will be used in place of the callback you would provide inlined on a barrister function call | this.callback = function(err, result) {
me.err = err;
me.result = result;
me.respReceived = true;
me._triggerCallback();
};
} |
| error() lets you register the callback function to invoke if the RPC call returns a JSON-RPC error. The callback is passed the err object. The This function supports chaining. For example: | Promise.prototype.error = function(callback, context) {
this.errCallback = callback;
this.errContext = context;
this._triggerCallback();
return this;
}; |
| success() lets you register the callback function to invoke if the RPC call does not return an errror. The callback is passed the result slot from the JSON-RPC response. The This function supports chaining. For example: | Promise.prototype.success = function(callback, context) {
this.resultCallback = callback;
this.resultContext = context;
this._triggerCallback();
return this;
}; |
| _triggerCallback() is a private function that calls the registered success/error callback once we get a response from the server. It flips an internal flag so that callbacks are only invoked once. | Promise.prototype._triggerCallback = function() {
if (this.callbackInvoked) {
return;
}
if (this.respReceived && this.err) { |
| If we receive an error we always want to invoke this branch, as we never want to call the success handler in this case. | if (this.errCallback) {
this.callbackInvoked = true;
if (this.errContext) {
this.errCallback.call(this.errContext, this.err);
}
else {
this.errCallback(this.err);
}
}
}
else if (this.respReceived && this.resultCallback) { |
| result can be null, so we don't test for this.result as long as there's no this.err, we can invoke this branch |
this.callbackInvoked = true;
if (this.resultContext) {
this.resultCallback.call(this.resultContext, this.result);
}
else {
this.resultCallback(this.result);
}
}
}; |
| The Contract class represents a single parsed IDL. Contracts contain interfaces, structs, and enums. It also encapsulates the type validation rules.
| function Contract(idl, coerce) {
var i, x, e, f;
this.idl = idl;
this.coerce = coerce;
this.interfaces = { };
this.functions = { };
this.structs = { };
this.enums = { };
this.meta = { }; |
| Separate the IDL into interfaces/structs/enums | for (i = 0; i < idl.length; i++) {
e = idl[i];
if (e.type === "interface") {
this.interfaces[e.name] = e;
for (x = 0; x < e.functions.length; x++) {
f = e.functions[x];
this.functions[e.name+"."+f.name] = f;
}
}
else if (e.type === "struct") {
this.structs[e.name] = e;
}
else if (e.type === "enum") {
this.enums[e.name] = e;
}
else if (e.type === "meta") {
for (x in e) {
if (e.hasOwnProperty(x) && x !== "type") {
this.meta[x] = e[x];
}
}
}
}
}
Contract.prototype.coerceRecursive = function(expectedType, isArray, val) {
var me = this;
var t, fields, e, i;
if (me.coerce && val !== null && val !== undefined) {
t = typeof val;
if (isArray === true) {
if (t !== "object" || !val instanceof Array) { |
| val isn't an array - bail | return val;
} |
| Recursively coerce all array members | for (i = 0; i < val.length; i++) {
val[i] = me.coerceRecursive(expectedType, false, val[i]);
}
return val;
}
else if (expectedType === "string" || expectedType === "bool" ||
expectedType === "int" || expectedType === "float") {
return me.coerce(expectedType, val);
}
else if (me.structs[expectedType]) { |
| we expect a user defined struct. val must be a JS object | if (t !== "object") {
return val;
} |
| get all fields for this struct, including fields on ancestors | fields = me.getAllStructFields([], me.structs[expectedType]);
for (i = 0; i < fields.length; i++) {
e = fields[i];
|
| recursively validate the field. return on any failure. | val[e.name] = me.coerceRecursive(e.type, e.is_array, val[e.name]);
}
return val;
}
}
|
| fall through | return val;
}; |
| validate takes an expected type and value and returns a two element array that indicates whether the value matches the expected type.
Params:
| Contract.prototype.validate = function(namePrefix, expected, isArray, val) {
var me = this;
var i, e, fields, fieldKeys, valid, isInt; |
| Returned when validation passes | var okRes = [ true, null ];
namePrefix = namePrefix || "";
if (val === null || val === undefined) { |
| if type was annotated as [optional], then null vals are OK | if (expected.optional === true) {
return okRes;
}
else {
return [false, namePrefix + " cannot be null"];
}
}
else {
var t = typeof val;
if (isArray === true) { |
| IDL expects an array. Reject val if it's not a JS Array | if (t !== "object" || !val instanceof Array) {
return me.validationErr(namePrefix, "[]"+expected.type, t, val);
} |
| Recursively validate all array members | for (i = 0; i < val.length; i++) {
valid = me.validate(namePrefix + "["+i+"]", expected, false, val[i]);
if (!valid[0]) {
return valid;
}
}
return okRes;
}
else if (expected.type === "string") {
return t === "string" ? okRes : me.validationErr(namePrefix, expected.type, t, val);
}
else if (expected.type === "bool") {
return t === "boolean" ? okRes : me.validationErr(namePrefix, expected.type, t, val);
}
else if (expected.type === "int" || expected.type === "float") {
if (t !== "number") {
return me.validationErr(namePrefix, expected.type, t, val);
} |
| This is our way of distinguishing floats from integers in JS See: http://bit.ly/zBGvzR | if (expected.type === "int") {
if (val % 1 !== 0) {
return me.validationErr(namePrefix, expected.type, "float", val);
}
}
return okRes;
}
else if (me.structs[expected.type]) { |
| we expect a user defined struct. val must be a JS object | if (t !== "object") {
return me.validationErr(namePrefix, expected.type, t, val);
} |
| get all fields for this struct, including fields on ancestors | fields = me.getAllStructFields([], me.structs[expected.type]);
fieldKeys = { };
for (i = 0; i < fields.length; i++) {
e = fields[i]; |
| recursively validate the field. return on any failure. | valid = me.validate(namePrefix+"."+e.name, e, e.is_array, val[e.name]);
if (!valid[0]) {
return valid;
} |
| keep track of fields on the struct | fieldKeys[e.name] = 1;
} |
| iterate through all keys in val and validate that they exist on the struct | for (i in val) {
if (val.hasOwnProperty(i) && !fieldKeys[i]) {
return [ false, namePrefix+"." + i + " does not exist in type '" +
expected.type + "'" ];
}
}
return okRes;
}
else if (me.enums[expected.type]) { |
| enum values should always be JS strings | if (t !== "string") {
return me.validationErr(namePrefix, expected.type, t, val);
} |
| validate that val is in the enum.values list from the IDL. could be optimized into a map down the road. | e = me.enums[expected.type];
for (i = 0; i < e.values.length; i++) {
if (e.values[i].value === val) {
return okRes;
}
} |
| didn't find it | var msg = namePrefix + " value '" + val + "' is not in the enum '" +
e.name + "': " + JSON.stringify(e.values);
return [ false, msg ];
}
else { |
| this is an unlikely branch and suggests the IDL is invalid | return [ false, namePrefix + " unknown type: " + expected.type ];
}
}
}; |
| validateReq accepts a JSON-RPC request object and checks the req.params array to ensure the length and types match the IDL for the function referenced in req.method Returns null if the request validates. Returns a JSON-RPC response object with the error property set if the request does not validate. | Contract.prototype.validateReq = function(req) { |
| special case for IDL request - never validate, as params are ignored by server | if (req.method === "barrister-idl") {
return null;
} |
| verify req.method exists on IDL | var func = this.functions[req.method];
if (!func) {
return errResp(req.id, -32601, "Method not found: " + req.method);
}
var paramLen = req.params ? req.params.length : 0;
var i, valid, msg; |
| verify req.params match the expected length for the IDL function | if (paramLen !== func.params.length) {
msg = "Param length: " + paramLen + " != expected length: " + func.params.length;
return errResp(req.id, -32602, msg);
}
|
| verify each req.params type matches the function param's expected type | for (i = 0; i < func.params.length; i++) {
valid = this.validate(func.params[i].name,
func.params[i],
func.params[i].is_array,
req.params[i]);
if (!valid[0]) {
req.params[i] = this.coerceRecursive(func.params[i].type, func.params[i].is_array, req.params[i]);
valid = this.validate(func.params[i].name,
func.params[i],
func.params[i].is_array,
req.params[i]);
}
if (!valid[0]) {
msg = "Invalid request param["+i+"]: " + valid[1];
return errResp(req.id, -32602, msg);
}
} |
| valid | return null;
};
Contract.prototype.validateResp = function(req, resp) { |
| ignore error responses and IDL requests | if (!resp.result || req.method === "barrister-idl") {
return resp;
} |
| verify req.method exists on IDL | var func = this.functions[req.method];
if (!func) {
return errResp(req.id, -32601, "Method not found: " + req.method);
}
var valid = this.validate("", func.returns, func.returns.is_array, resp.result);
if (!valid[0]) {
var msg = "Invalid response for " + req.method + ": " + valid[1];
if (typeof console !== "undefined" && console.log) {
console.log("ERROR: " + msg);
}
return errResp(req.id, -32001, msg);
}
else {
return resp;
}
}; |
| getAllStructFields returns an array of fields for the given struct and its ancestors. It's recursive. Params:
| Contract.prototype.getAllStructFields = function(fields, struct) {
if (struct.fields.length > 0) {
fields = fields.concat(struct.fields);
}
if (struct['extends']) {
return this.getAllStructFields(fields, this.structs[struct['extends']]);
}
else {
return fields;
}
}; |
| validationErr returns a two element array indicating a type validation error. Used by validate() function | Contract.prototype.validationErr = function(namePrefix, expType, actType, val) {
return [ false, namePrefix + " expects type '" + expType + "' but got type '" +
actType + "' for value: " + val ];
}; |
| The Batch class is used to batch multiple requests against a server in a single roundtrip.
| function Batch(client) {
this.client = client;
this.reqList = [];
} |
| proxy returns an object for the given interface that can be used to make RPC calls for that interface | Batch.prototype.proxy = function(ifaceName) {
var me = this;
return me.client._proxy(function(method) { return me._functionProxy(method); }, ifaceName);
}; |
| Used by the proxy objects - overrides the Client.request behavior. Instead of making a call to the server, we simply push the request on an array. | Batch.prototype.request = function(method, params) {
this.reqList.push(makeRequest(method, params));
}; |
| Internal function to dispense a function for calling the given method | Batch.prototype._functionProxy = function(method) {
var client = this;
return function() { |
| unlike Client._functionProxy, don't pop the last arg since batch invocations don't supply callbacks | var args = Array.prototype.slice.call(arguments);
client.request(method, args);
};
}; |
| Sends the batch to the server The callback will be passed two parameters:
results is array of JSON-RPC result objects. test for these keys:
| Batch.prototype.send = function(callback) {
var me = this;
var i, r, errObj; |
| map of request ids to response objects | var idToResp = { }; |
| requests to send to server | var reqList = []; |
| iterate through requests queued on this batch, validate them, and copy them to the errors list or reqList | for (i = 0; i < me.reqList.length; i++) {
r = me.reqList[i];
if (me.client.validateRequest) {
errObj = me.client.contract.validateReq(r);
if (errObj) {
idToResp[r.id] = errObj;
}
else {
reqList.push(r);
}
}
else {
reqList.push(r);
}
}
var genResponseArr = function(reqList, idToResp) {
var respList = [ ];
var respObj;
for (i = 0; i < reqList.length; i++) {
if (reqList[i].id) {
r = idToResp[reqList[i].id];
if (r) {
r = me.wrapResp(reqList[i], r);
if (r.error) {
respObj = { error: r.error };
}
else {
respObj = { result: r.result };
}
}
else {
msg = "No response received for request id: " + reqList[i].id;
respObj = { error: me.wrapResp(reqList[i], errResp(-32603, msg)) };
}
respList.push(me.wrapResp(reqList[i], respObj));
}
}
return respList;
}; |
| We have no valid requests, so hit the callback immediately. This could occur if the batch were empty, or if all requests in the batch failed type validation | if (reqList.length === 0) {
callback(null, genResponseArr(me.reqList, idToResp));
return;
} |
| Send the valid requests to the server | me.client._send(reqList, function(resp) {
if (resp !== null && resp !== undefined && resp instanceof Array) { |
| reorder results to match the order of the requests, as server may return them in a different order | var results = [ ];
var i, r, msg;
for (i = 0; i < resp.length; i++) {
r = resp[i];
if (r.id) {
idToResp[r.id] = r;
}
} |
| iterate through reqList and find the response matching each request id. push onto the errors list or results list depending on whether the response was successful or not. | callback(null, genResponseArr(me.reqList, idToResp));
}
else { |
| single error - probably a transport related issue we can't correlate this to any single request, so pass an empty req object to wrapResp | callback(resp, null);
}
});
}; |
| Adds the request id and params to the response so that the batch send() callback | Batch.prototype.wrapResp = function(req, resp) {
resp.method = req.method;
resp.params = req.params;
return resp;
}; |
| The Client class is responsible for making requests to the server using the given transport function.
| function Client(transport, opts) {
this.transport = transport;
this.trace = null;
this.coerce = null; |
| You may set this to false to disable client side request validation | this.validateRequest = true;
if (opts && opts.coerce) {
if (opts.coerce === true) {
this.coerce = defaultCoerce;
}
else if (typeof opts.coerce === "function") {
this.coerce = opts.coerce;
}
}
} |
| laodContract requests the IDL from the server and sets a Contract object on the client if the request succeeds.
If callback is not passed in, a Promise will be returned. | Client.prototype.loadContract = function(callback) {
var me = this;
var onResponse = null;
var promise = null;
if (!callback) {
promise = new Promise();
callback = promise.callback;
}
me.request("barrister-idl", [], function (err, result) {
if (err) {
callback(err);
}
else {
me.contract = new Contract(result, me.coerce);
callback(null, result);
}
});
return promise;
}; |
| getMeta returns an object of name/value pair metadata for the client's Contract | Client.prototype.getMeta = function() {
return this.contract.meta;
}; |
| enableTrace turns on request/response logging
| Client.prototype.enableTrace = function(logFunc) {
if (logFunc && (typeof logFunc === "function")) {
this.trace = logFunc;
}
else {
this.trace = function(s) { console.log(s); };
}
}; |
| disableTrace turns off request/response logging | Client.prototype.disableTrace = function() {
this.trace = null;
}; |
| startBatch returns a new Batch object associated with this Client | Client.prototype.startBatch = function() {
return new Batch(this);
}; |
| proxy returns a new proxy object for the given interface. If loadContract() has not successfully completed, or no interface exists with the given name, an exception will be thrown. | Client.prototype.proxy = function(ifaceName) {
var me = this;
return me._proxy(function(method) { return me._functionProxy(method); }, ifaceName);
}; |
| Internal function for creating a proxy that is shared by Client and Batch.
| Client.prototype._proxy = function(funcProxyCreator, ifaceName) {
var me = this;
if (!me.contract) {
throw "Contract.loadContract() has not been called yet!";
}
var iface = me.contract.interfaces[ifaceName];
if (iface) {
var proxy = { };
var i, func;
for (i = 0; i < iface.functions.length; i++) {
func = iface.functions[i];
proxy[func.name] = funcProxyCreator(ifaceName+"."+func.name);
}
return proxy;
}
else {
throw "Interface not found: " + ifaceName;
}
}; |
| Creates a function proxy for Client, which will make a request to the server when called. This function proxy is what your code actually invokes when you call a RPC function. Pass in the arguments that the RPC function expects, plus an optional callback function. If the callback function is not included, a Promise object is returned. | Client.prototype._functionProxy = function(method) {
var client = this;
|
| Generate the proxy function | return function() {
var callback = null,
lastArg = null,
promise = null,
args = Array.prototype.slice.call(arguments);
if (arguments.length > 0) { |
| Check to see if the last argument is a function. If so, we pop it from the args array, and use that as the callback function to invoke when the response is received. | lastArg = arguments[arguments.length-1];
if (lastArg !== undefined && lastArg !== null && typeof lastArg === "function") {
callback = args.pop();
}
}
|
| If no callback function was provided, create a Promise object and use that to handle the response. You are then expected to call success() and error() on the promise to register your callbacks. | if (!callback) {
promise = new Promise();
callback = promise.callback;
} |
| sanity check | if (callback === undefined || callback === null || typeof callback !== "function") {
throw "Last arg to " + method + " must be a callback function!";
}
client.request(method, args, callback);
return promise;
};
}; |
| request makes a single request to the server | Client.prototype.request = function(method, params, callback) {
var me = this; |
| turn method/params into a JSON-RPC request object | var req = makeRequest(method, params); |
| optionally validate request | if (me.validateRequest && me.contract) {
var err = me.contract.validateReq(req);
if (err) {
callback(err.error, null);
return;
}
} |
| send request to server | me._send(req, function(resp) { |
| check for JSON-RPC error | if (resp.error) {
callback(resp.error, null);
}
else { |
| Successful response, pass result to callback | callback(null, resp.result);
}
});
}; |
| _send makes the request to the server | Client.prototype._send = function(req, callback) {
var me = this; |
| log request if trace is enabled | if (me.trace) {
me.trace("Request: " + JSON_stringify(req));
} |
| make request using transport provided to Client constructor | me.transport(req, function(resp) { |
| log response if trace is enabled | if (me.trace) {
me.trace("Response: " + JSON_stringify(resp));
}
callback(resp);
});
}; |
| inprocClient returns a Client instance that calls the given Server directly in process. This is useful in cases where you develop separate barrister components as separate packages, but wind up combining them into a single program at runtime. It is also useful for unit testing barrister server implementations because your tests can consume the service as a Barrister client, which will perform all the type checks on requests/response values. This will catch many problems automatically that you'd normally have to write assertions for (e.g. function returned an int, but you expected a bool) | var inprocClient = function(server, opts) {
var transport = function(req, callback) {
server.handle({}, req, callback);
};
return new Client(transport, opts);
}; |
| okResp formats id and result as a JSON-RPC result object | function okResp(id, result) {
id = id || null;
return { "jsonrpc": "2.0", "id": id, "result": result };
} |
| The Server class holds handlers that implement the interfaces on the given IDL. This constructor creates a Contract for the given idl that is used to validate requests and responses. | function Server(idl) {
this.handlers = { };
this.filters = null;
this.contract = new Contract(idl);
this.trace = null;
} |
| Sets the filters for this Server. Allows a single object, or an array. Pass in null to clear existing filters. Filter objects should contain
Example: | Server.prototype.setFilters = function(filters) {
var arr = null;
if (filters !== null && filters !== undefined) {
arr = [ ];
var t = typeof filters;
if (filters instanceof Array) {
arr = arr.concat(filters);
}
else if (t === "object") {
arr.push(filters);
}
else {
throw "filters must be an object or array";
}
}
this.filters = arr;
}; |
| addHandler associates the given handler object with the interface. The handler functions will be called when an incoming request is processed with the matching interface name.
| Server.prototype.addHandler = function(iface, handler) {
this.handlers[iface] = handler;
}; |
| handleJSON accepts a string encoded as JSON, parses the JSON, and calls handle() with the parsed JSON-RPC request.
| Server.prototype.handleJSON = function(props, reqJSON, callback) {
var req;
try {
req = JSON.parse(reqJSON);
}
catch (e) {
callback(JSON_stringify(errResp(null, -32700, "Unable to parse JSON: " + reqJSON)));
return;
}
this.handle(props, req, function(resp) {
callback(JSON_stringify(resp));
});
}; |
| handle processes a JSON-RPC request which may be a batch or single request. Batch request elements will be processed concurrently. Same parameters as handleJSON, except req is an object (not string) | Server.prototype.handle = function(props, req, callback) {
var me = this;
var resp, i; |
| Check if req is a batch | if (req instanceof Array) {
resp = [ ]; |
| Create a callback for each request element that pushes the result on the resp array. | var reqCb = function(res) {
resp.push(res); |
| If all requests have been processed, then batch is complete | if (resp.length === req.length) {
return callback(resp);
}
}; |
| Check for empty batch, and start processing each request concurrently. | if (req.length > 0) {
for (i = 0 ; i < req.length; i++) {
me.handleSingle(props, req[i], reqCb);
}
}
else {
callback(errResp(req.id, -32600, "Request contains empty batch"));
}
}
else { |
| Single request case | me.handleSingle(props, req, callback);
}
}; |
| handleSingle processes a single JSON-RPC request (not a batch) | Server.prototype.handleSingle = function(props, req, callback) {
var me = this;
var key;
var context = {
props: props,
request: req
};
me._runFilters("pre", context, function() { |
| If a filter sets context.error with a numeric error code, then skip request execution and return the error | if (context.error && typeof context.error.code === "number") {
callback(errResp(req.id, parseInt(context.error.code, 10),
context.error.message, context.error.data));
}
else { |
| Execute the handler for this request | me._execute(req, function(resp) {
context.response = resp;
me._runFilters("post", context, function() { |
| Validate the resp object and hit callback | callback(me.contract.validateResp(req, resp));
});
});
}
});
}; |
| Executes all filters set on the Server
If a filter does not define a function for the hook then it will be silently skipped. | Server.prototype._runFilters = function(hook, context, callback) {
var me = this;
var nextFilter, filter;
if (me.filters && me.filters.length > 0) {
filters = [];
filters = filters.concat(me.filters);
nextFilter = function() {
if (filters.length > 0) {
filter = filters.shift();
if (filter[hook]) {
filter[hook](context, function() {
nextFilter();
});
}
else {
nextFilter();
}
}
else {
callback();
}
};
nextFilter();
}
else {
callback();
}
};
Server.prototype._execute = function(req, callback) {
var me = this;
var i, msg, errObj;
if (req.method) { |
| Special case for IDL request | if (req.method === "barrister-idl") {
return callback(okResp(req.id, me.contract.idl));
} |
| JSON-RPC methods are a single string which Barrister encodes
as | var pos = req.method.indexOf(".");
if (pos === -1) {
return callback(errResp(req.id, -32601, "Method not found: " + req.method));
}
var ifaceName = req.method.substring(0, pos);
var funcName = req.method.substring(pos+1);
var iface = me.contract.interfaces[ifaceName];
var func = me.contract.functions[req.method];
var handler = me.handlers[ifaceName];
if (iface && func && handler && handler[funcName]) { |
| Reject request if params do not match IDL | errObj = me.contract.validateReq(req);
if (errObj !== null) {
return callback(errObj);
} |
| Create a new array to hold the params to invoke the function with | var callParams = [];
var paramLen = req.params ? req.params.length : 0;
if (paramLen > 0) {
callParams = callParams.concat(req.params);
} |
| Always push a callback function onto the callParams array, which the handler function implementation should call with the result from the function. | callParams.push(function(err, result) {
if (err === null || err === undefined) {
callback(okResp(req.id, result));
}
else {
var t = typeof err;
var errObj; |
| Error case. Figure out what type err is and encode as JSON-RPC error accordingly. | if (t === "object") {
if (typeof err.code === "number") {
errObj = errResp(req.id, err.code, err.message, err.data);
}
else {
errObj = errResp(req.id, -32000, "Unknown error", err);
}
}
else if (t === "number") {
errObj = errResp(req.id, parseInt(err, 10), "Server error: " + err);
}
else if (t === "string") {
errObj = errResp(req.id, -32000, err);
}
else {
console.log("Barrister passed invalid err type: " + t);
errObj = errResp(req.id, -32000, "Unknown error");
}
callback(errObj);
}
}); |
| Key line: invoke the handler function with the given params | handler[funcName].apply(handler, callParams);
}
else {
return callback(errResp(req.id, -32601, "Method not found: " + req.method));
}
}
else {
return callback(errResp(req.id, -32600, "Request did not contain a method"));
}
}; |
| httpClient returns a Client instance for the given URL endpoint opts is an optional object that is passed to the Client -- see the Client constructor for details | var httpClient = function(endpoint, opts) {
var httpTransport = function(req, callback) {
var reqJson = JSON_stringify(req);
var options = {
url: endpoint,
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: reqJson
};
request(options, function(error, response, body) {
callback(parseResponse(req, error, body));
});
};
return new Client(httpTransport, opts);
}; |
| Export blessed functions to the package | exports.httpClient = httpClient;
exports.inprocClient = inprocClient;
exports.Client = Client;
exports.Server = Server;
exports.JSON_stringify = JSON_stringify;
|