/
utils.js
210 lines (186 loc) · 5.89 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
var util = require('util');
/** * @namespace */
var Utils = module.exports;
/**
* Generates a JSON-RPC 2.0 request
* @param {String} method Name of method to call
* @param {Array|Object} params Array of parameters passed to the method as specified, or an object of parameter names and corresponding value
* @param {String|Number|null} [id] Request ID can be a string, number, null for explicit notification or left out for automatic generation
* @param {Object} [options] Optional name => value pairs of settings
* @throws {TypeError} If any of the parameters are invalid
* @return {Object} A JSON-RPC 2.0 request
* @api public
*/
Utils.request = function(method, params, id, options) {
if(typeof(method) !== 'string') {
throw new TypeError(method + ' must be a string');
}
if(!params || (typeof(params) !== 'object' && !Array.isArray(params))) {
throw new TypeError(params + ' must be an object or an array');
}
options = options || {};
var request = {
jsonrpc: "2.0",
params: params,
method: method
};
// if id was left out, generate one (null means explicit notification)
if(typeof(id) === 'undefined') {
var generator = typeof(options.generator) === 'function' ? options.generator : Utils.generateId;
request.id = generator(request);
} else {
request.id = id;
}
return request;
};
/**
* Generates a JSON-RPC 2.0 response
* @param {Object} error Error member
* @param {Object} result Result member
* @param {String|Number|null} id Id of request
* @return {Object} A JSON-RPC 2.0 response
* @api public
*/
Utils.response = function(error, result, id) {
id = typeof(id) === 'undefined' || id === null ? null : id;
var response = { jsonrpc: "2.0", id: id };
// one or the other with precedence for errors
if(error) response.error = error;
else response.result = result;
return response;
};
/**
* Generates a random id
* @return {Number}
* @api public
*/
Utils.generateId = function() {
return Math.round(Math.random() * Math.pow(2, 24));
};
/**
* Merges properties of object b into object a
* @param {Object} a
* @param {Object} b
* @return {Object}
* @api private
*/
Utils.merge = function(a, b){
if (a && b) {
for (var key in b) {
a[key] = b[key];
}
}
return a;
};
/**
* Helper to parse a HTTP request body and interpret it as JSON
* @param {ServerRequest} req node.js ServerRequest instance
* @param {Function} [reviver] Optional reviver for JSON.parse
* @param {Function} callback
* @return {void}
* @api public
*/
Utils.parseBody = function(req, reviver, callback) {
if(!callback && typeof(reviver) === 'function') {
callback = reviver;
reviver = null;
}
var data = '';
req.setEncoding('utf8');
req.on('data', function(chunk) { data += chunk; });
req.on('end', function() {
try {
var request = JSON.parse(data, reviver);
} catch(err) {
return callback(err);
}
callback(null, request);
});
};
/**
* Wraps a server instance around a HTTP request listener (used by the HTTP and HTTPS server modules)
* @param {JaysonServer} server Instance of JaysonServer (typically jayson.Server.http or jayson.Server.https)
* @param {Object} [options] Optional name => value pairs of settings
* @return {Function}
* @api private
*/
Utils.httpRequestWrapper = function(server, options) {
return function(req, res) {
options = Utils.merge(server.options, options || {});
// 405 method not allowed if not POST
if(!Utils.isMethod(req, 'POST')) return respondError('Method not allowed', 405, { 'allow': 'POST' });
// 415 unsupported media type if content-type is not correct
if(!Utils.isContentType(req, 'application/json')) return respondError('Unsupported media type', 415);
Utils.parseBody(req, options.reviver, function(err, request) {
// parsing failed, 500 server error
if(err) return respondError(err, 500);
server.call(request, function(error, success) {
var response = error || success;
if(response) {
var body = '';
// stringifies JSON
try {
body = JSON.stringify(response, options.replacer);
} catch(err) {
return respondError(err, 400);
}
var headers = {
"content-length": Buffer.byteLength(body, options.encoding),
"content-type": "application/json"
};
res.writeHead(200, headers);
res.write(body);
res.end();
} else {
// no response received at all, must be a notification
res.writeHead(204);
res.end();
}
});
});
// ends the request with an error code
function respondError(err, code, headers) {
res.writeHead(code, headers || {});
res.end(String(err));
}
};
};
/**
* Determines if a HTTP Request comes with a specific content-type
* @param {ServerRequest} request
* @param {String} type
* @return {Boolean}
* @api private
*/
Utils.isContentType = function(request, type) {
request = request || {headers: {}};
var contentType = request.headers['content-type'] || '';
return RegExp(type, 'i').test(contentType);
};
/**
* Determines if a HTTP Request is of a specific method
* @param {ServerRequest} request
* @param {String} method
* @return {Boolean}
* @api private
*/
Utils.isMethod = function(request, method) {
method = (method || '').toUpperCase();
return (request.method || '') === method;
};
/**
* Determines the parameter names of a function
* @param {Function} func
* @return {Array}
* @api private
*/
Utils.getParameterNames = function(func) {
if(typeof(func) !== 'function') return [];
var body = func.toString();
var args = /^function \((.+?)\)/.exec(body);
if(!args) return [];
var list = (args.pop() || '').split(',');
return list.map(function(arg) { return arg.trim(); });
};
// For quick access in classes
Utils.inherits = util.inherits;