1 | var AV = require('avoscloud-sdk').AV;
|
2 | var underscore = require('underscore');
|
3 | var http = require('http');
|
4 | var https = require('https');
|
5 | var urlParser = require('url');
|
6 | var querystring = require('querystring');
|
7 | var util = require('util');
|
8 | var express = require('express');
|
9 | var path = require('path');
|
10 | var fs = require('fs');
|
11 | var _ = require('underscore');
|
12 | var cronJob = require('cron').CronJob;
|
13 | var qs = require('qs');
|
14 |
|
15 | var Global = {}
|
16 |
|
17 | var _ref, _ref1;
|
18 | if ((_ref = https.globalAgent) != null) {
|
19 | if ((_ref1 = _ref.options) != null) {
|
20 | _ref1.rejectUnauthorized = false;
|
21 | }
|
22 | }
|
23 |
|
24 |
|
25 | function MockRequest(object, params, user){
|
26 | this.object = object;
|
27 | this.params = params || object;
|
28 | this.user = user;
|
29 | }
|
30 |
|
31 |
|
32 | function MockResponse(options){
|
33 | this._options = options;
|
34 | }
|
35 |
|
36 | MockResponse.prototype = {
|
37 | success: function(data){
|
38 | this._options.success(data);
|
39 | },
|
40 | error: function(err){
|
41 | this._options.error(err);
|
42 | }
|
43 | }
|
44 |
|
45 | exports.MockRequest = MockRequest
|
46 | exports.MockResponse = MockResponse
|
47 |
|
48 |
|
49 |
|
50 |
|
51 | var HOOK_PREFIX = "_hook_";
|
52 | var className = function(clazz) {
|
53 | if (underscore.isString(clazz)) {
|
54 | return HOOK_PREFIX + clazz;
|
55 | }
|
56 | if (clazz.className != null) {
|
57 | return HOOK_PREFIX + clazz.className;
|
58 | }
|
59 | throw "Unknown class:" + clazz;
|
60 | };
|
61 |
|
62 |
|
63 | Global.funcs = {};
|
64 | AV.Cloud.define = function(name, func){
|
65 | Global.funcs[name] = func;
|
66 | };
|
67 | AV.Cloud.beforeSave = function(name, func){
|
68 | Global.funcs[className(name) + "_beforeSave"] = func;
|
69 | };
|
70 | AV.Cloud.afterSave = function(name, func){
|
71 | Global.funcs[className(name) + "_afterSave"] = func;
|
72 | };
|
73 | AV.Cloud.afterUpdate = function(name, func){
|
74 | Global.funcs[className(name) + "_afterUpdate"] = func;
|
75 | };
|
76 | AV.Cloud.beforeDelete = function(name, func){
|
77 | Global.funcs[className(name) + "_beforeDelete"] = func;
|
78 | };
|
79 | AV.Cloud.afterDelete = function(name, func){
|
80 | Global.funcs[className(name) + "_afterDelete"] = func;
|
81 | };
|
82 |
|
83 | function runFunc(name, req, res){
|
84 | if(!Global.funcs[name])
|
85 | throw "Could not find function:" + name;
|
86 | Global.funcs[name].call(this, req, res);
|
87 | }
|
88 | function runBeforeSave(name, req, res){
|
89 | runFunc(className(name) + "_beforeSave", req, res);
|
90 | }
|
91 | function runAfterSave(name, req, res){
|
92 | runFunc(className(name) + "_afterSave", req, res);
|
93 | }
|
94 | function runAfterUpdate(name, req, res){
|
95 | runFunc(className(name) + "_afterUpdate", req, res);
|
96 | }
|
97 | function runBeforeDelete(name, req, res){
|
98 | runFunc(className(name) + "_beforeDelete", req, res);
|
99 | }
|
100 | function runAfterDelete(name, req, res){
|
101 | runFunc(className(name) + "_afterDelete", req, res);
|
102 | }
|
103 |
|
104 | exports.runFunc = runFunc
|
105 | exports.runBeforeSave = runBeforeSave
|
106 | exports.runAfterSave = runAfterSave
|
107 | exports.runAfterUpdate = runAfterUpdate
|
108 | exports.runBeforeDelete = runBeforeDelete
|
109 | exports.runAfterDelete = runAfterDelete
|
110 |
|
111 | AV.Cloud.setInterval = function(name, interval, func){
|
112 | if (!/[a-zA-Z0-9]+/.exec(name)) {
|
113 | throw "The timer name must be an valid identifier.";
|
114 | }
|
115 | if ((typeof func) !== 'function') {
|
116 | throw "The func must be a function.";
|
117 | }
|
118 | if ((typeof interval) !== 'number') {
|
119 | throw "The interval must be a valid integer in seconds.";
|
120 | }
|
121 | new cronJob('*/'+interval+' * * * * *', func, null, true);
|
122 | };
|
123 |
|
124 | AV.Cloud.cronJob = function(name, cron, func) {
|
125 |
|
126 | if (!/[a-zA-Z0-9]+/.exec(name)) {
|
127 | throw "The timer name must be an valid identifier.";
|
128 | }
|
129 | if ((typeof func) !== 'function') {
|
130 | throw "The func must be a function.";
|
131 | }
|
132 | if ((typeof cron) !== 'string') {
|
133 | throw "The cron must be a valid string in the form of 'sec min hour dayOfMonth month dayOfWeek [year]'.";
|
134 | }
|
135 | if (cron.split(" ").length < 6) {
|
136 | throw "The cron must be a valid string in the form of 'sec min hour dayOfMonth month dayOfWeek [year]'.";
|
137 | }
|
138 | new cronJob(cron, func, null, true);
|
139 | };
|
140 |
|
141 | HTTPResponse = (function() {
|
142 | function HTTPResponse(buffer, headers, response, status, text) {
|
143 | this.buffer = buffer != null ? buffer : null;
|
144 | this.headers = headers != null ? headers : {};
|
145 | this.response = response != null ? response : null;
|
146 | this.status = status != null ? status : null;
|
147 | this.text = text != null ? text : null;
|
148 | }
|
149 |
|
150 | return HTTPResponse;
|
151 |
|
152 | })();
|
153 |
|
154 | mimeTypes = [
|
155 | {
|
156 | pattern: /^text\/plain.*/i,
|
157 | process: function(res) {
|
158 | return res.text;
|
159 | }
|
160 | }, {
|
161 | pattern: /^application\/json.*/i,
|
162 | process: function(res) {
|
163 | return JSON.parse(res.text);
|
164 | }
|
165 | }, {
|
166 | pattern: /^application\/x-www-form-urlencoded/i,
|
167 | process: function(res) {
|
168 | return qs.parse(res.buffer);
|
169 | }
|
170 | }
|
171 | ];
|
172 |
|
173 | trySetData = function(httpRes) {
|
174 | var contentType, type;
|
175 |
|
176 | contentType = httpRes.headers['content-type'];
|
177 | type = _.find(mimeTypes, function(mimeType) {
|
178 | return mimeType.pattern.exec(contentType);
|
179 | });
|
180 | if (type != null) {
|
181 | try{
|
182 | return httpRes.data = type.process(httpRes);
|
183 | }catch(e){
|
184 | httpRes.data = httpRes.buffer;
|
185 | }
|
186 | } else {
|
187 | return httpRes.data = httpRes.buffer;
|
188 | }
|
189 | };
|
190 |
|
191 | AV.Cloud.HTTPResponse = HTTPResponse;
|
192 |
|
193 | AV.Cloud.httpRequest = function(options) {
|
194 | var body, headers, hostname, httpResponse, http_module, method, params, parsedRes, path, port, promise, request, requestOptions, search, url;
|
195 |
|
196 | options = options || {};
|
197 | options.agent = false;
|
198 | url = options.url;
|
199 | http_module = /^https.*/.exec(url) ? https : http;
|
200 | promise = new AV.Promise();
|
201 | params = options.params;
|
202 | headers = options.headers || "";
|
203 | method = options.method || "GET";
|
204 | body = options.body;
|
205 | parsedRes = urlParser.parse(url);
|
206 | hostname = parsedRes.hostname;
|
207 | port = parsedRes.port || 80;
|
208 | path = parsedRes.path;
|
209 | search = parsedRes.search;
|
210 | if (params != null) {
|
211 | path = search == null ? path + '?' : path + '&';
|
212 | if (typeof params === 'string') {
|
213 | params = querystring.parse(params);
|
214 | }
|
215 | params = querystring.stringify(params);
|
216 | path = path + params;
|
217 | }
|
218 | delete options.params;
|
219 | delete options.body;
|
220 | delete options.url;
|
221 | requestOptions = {
|
222 | host: hostname,
|
223 | port: port,
|
224 | method: method,
|
225 | headers: headers,
|
226 | path: path
|
227 | };
|
228 | requestOptions = _.extend(requestOptions, options);
|
229 | httpResponse = new HTTPResponse;
|
230 | request = http_module.request(requestOptions, function(res) {
|
231 | var chunkList, contentLength;
|
232 | httpResponse.headers = res.headers || {};
|
233 | httpResponse.status = res.statusCode;
|
234 | httpResponse.text = '';
|
235 | chunkList = [];
|
236 | contentLength = 0;
|
237 | res.on('data', function(chunk) {
|
238 | httpResponse.text += chunk.toString();
|
239 | contentLength += chunk.length;
|
240 | return chunkList.push(chunk);
|
241 | });
|
242 | return res.on('end', function() {
|
243 | var chunk, pos, _i, _len;
|
244 |
|
245 | httpResponse.buffer = new Buffer(contentLength);
|
246 | pos = 0;
|
247 | for (_i = 0, _len = chunkList.length; _i < _len; _i++) {
|
248 | chunk = chunkList[_i];
|
249 | chunk.copy(httpResponse.buffer, pos);
|
250 | pos += chunk.length;
|
251 | }
|
252 | trySetData(httpResponse);
|
253 | if (httpResponse.status < 200 || httpResponse.status >= 400) {
|
254 | return promise.reject(httpResponse);
|
255 | } else {
|
256 | return promise.resolve(httpResponse);
|
257 | }
|
258 | });
|
259 | });
|
260 | request.on('error', function(e) {
|
261 | httpResponse.text = util.inspect(e);
|
262 | httpResponse.status = 500;
|
263 | return promise.reject(httpResponse);
|
264 | });
|
265 | request.end(body);
|
266 | return promise._thenRunCallbacks(options);
|
267 | };
|
268 |
|
269 | Global.files = {}
|
270 |
|
271 | function watchFile(f, name){
|
272 | if(Global.files[f])
|
273 | return;
|
274 | Global.files[f] = true;
|
275 | fs.watchFile(f,{ persistent: true, interval: 2000 },function(curr, prev){
|
276 | if(curr.mtime != prev.mtime){
|
277 | console.log("File " + f + " is changed,reload it...");
|
278 | requireFromFile(f, name);
|
279 | }
|
280 | });
|
281 | }
|
282 |
|
283 |
|
284 | var Module = module.constructor;
|
285 | var paths = module.paths;
|
286 | function requireFromFile(path, filename) {
|
287 | var src = fs.readFileSync(path, 'utf-8');
|
288 | var m = new Module();
|
289 | m.paths = module.paths;
|
290 | m._compile("var AV = require('avoscloud-sdk').AV;var __production=0; \n" + src, filename);
|
291 | watchFile(path,filename);
|
292 | return m.exports;
|
293 | }
|
294 |
|
295 |
|
296 | Module.prototype.require = function(id) {
|
297 | if(id.match(/^cloud\//)){
|
298 | id = Global.rootPath + id;
|
299 | return requireFromFile(require.resolve(id), id);
|
300 | }
|
301 | result = Module._load(id, this);
|
302 | if(id == 'express'){
|
303 | oldExpress = result;
|
304 | result = function(){
|
305 | if(Global.app!=null){
|
306 | delete Global.app.routes.get;
|
307 | delete Global.app.routes.post;
|
308 | delete Global.app.routes.put;
|
309 | delete Global.app.routes.delete;
|
310 | delete Global.app.routes.options;
|
311 | addSystemEndpoints(Global.app);
|
312 | return Global.app;
|
313 | }
|
314 | var app = oldExpress();
|
315 | app.__listen = app.listen;
|
316 | app.listen = function(){
|
317 | var configDir, jsonFile, publicDir, views;
|
318 | jsonFile = require.resolve(Global.rootPath + 'config/global.json');
|
319 | configDir = path.dirname(jsonFile);
|
320 | views = path.resolve(configDir, '../' + (this.get('views')));
|
321 | publicDir = path.resolve(configDir, '../public');
|
322 | this.set('views', views);
|
323 | this.use(oldExpress["static"](publicDir));
|
324 | this.use(function(err, req, res, next) {
|
325 | if (err != null) {
|
326 | console.error("Error occured:" + err);
|
327 | return res.send(err);
|
328 | } else {
|
329 | return next();
|
330 | }
|
331 | });
|
332 | return this;
|
333 | };
|
334 | Global.app = app;
|
335 | return app;
|
336 | };
|
337 | result = _.extend(result, oldExpress);
|
338 | }
|
339 | return result;
|
340 | };
|
341 |
|
342 | var createObject = function(req, res, cb){
|
343 | var className = req.params.className;
|
344 | var object = new AV.Object(className);
|
345 | var body = req.body;
|
346 | if(body.id != null && body.id != ''){
|
347 | object = AV.Object.createWithoutData(className, body.id);
|
348 | object.fetch().then(function(obj){
|
349 | cb.call(this, object);
|
350 | }, function(err){
|
351 | res.send('Error : ' + err.message);
|
352 | });
|
353 | }else{
|
354 | object._finishFetch(req.body, true);
|
355 | cb.call(this, object);
|
356 | }
|
357 | }
|
358 |
|
359 | function processRequest(type, req, res){
|
360 | if(type == 'object'){
|
361 | var func = req.params.func;
|
362 | var className = req.params.className;
|
363 | createObject(req, res, function(object){
|
364 | var mockReq = new MockRequest(object);
|
365 | var mockResp =new MockResponse({
|
366 | success: function(data){
|
367 | res.send("ok.");
|
368 | },
|
369 | error: function(err){
|
370 | console.log("Error occured:" + err);
|
371 | res.send("Error : " + err);
|
372 | }
|
373 | });
|
374 | var target = null;
|
375 | switch(func){
|
376 | case "beforeSave":
|
377 | target = runBeforeSave;
|
378 | break;
|
379 | case "afterSave":
|
380 | target = runAfterSave;
|
381 | break;
|
382 | case "afterUpdate":
|
383 | target = runAfterUpdate;
|
384 | break;
|
385 | case "beforeDelete":
|
386 | target = runBeforeDelete;
|
387 | break;
|
388 | case "afterDelete":
|
389 | target = runAfterDelete;
|
390 | break;
|
391 | default:
|
392 | throw "Could not find function:" + func;
|
393 | }
|
394 | target.call(this, className, mockReq, mockResp);
|
395 | });
|
396 | }else{
|
397 | var mockReq = new MockRequest(null, req.body);
|
398 | var mockResp =new MockResponse({
|
399 | success: function(data){
|
400 | res.send(data);
|
401 | },
|
402 | error: function(err){
|
403 | console.log("Error occured:" + err);
|
404 | res.send("Error : " + err);
|
405 | }
|
406 | });
|
407 | runFunc(req.params.name, mockReq, mockResp);
|
408 | }
|
409 | }
|
410 | var lib = path.join(path.dirname(fs.realpathSync(__filename)), '../lib');
|
411 | var extractFuncs = function(cb){
|
412 | var funcs = [];
|
413 | var classes = [];
|
414 | for(var f in Global.funcs){
|
415 | if(!new RegExp('^' + HOOK_PREFIX).exec(f)){
|
416 | funcs.push(f);
|
417 | }else{
|
418 | var idx = f.lastIndexOf("_");
|
419 | var className = f.substring(HOOK_PREFIX.length, idx);
|
420 | var method = f.substring(idx + 1);
|
421 | classes[className] = classes[className] || []
|
422 | classes[className].push(method);
|
423 | }
|
424 | }
|
425 | cb.call(this, funcs, classes);
|
426 | }
|
427 | function addSystemEndpoints(app){
|
428 |
|
429 | app.post("/avos/:className/:func", function(req, res){
|
430 | processRequest('object', req, res);
|
431 | });
|
432 | app.post("/avos/:name", function(req, res){
|
433 | processRequest("function", req, res);
|
434 | });
|
435 | app.get("/avos", function(req, res){
|
436 | res.sendfile(lib + "/index.html");
|
437 | });
|
438 | app.get("/avos/classes", function(req, res){
|
439 | extractFuncs(function(funcs, classes){
|
440 | res.send(_.keys(classes));
|
441 | });
|
442 | });
|
443 | app.get("/avos/functions", function(req, res){
|
444 | extractFuncs(function(funcs, classes){
|
445 | var className = req.query.className;
|
446 | if(className == null){
|
447 | res.send(funcs);
|
448 | }else{
|
449 | res.send(classes[className] || []);
|
450 | }
|
451 | });
|
452 | });
|
453 | }
|
454 |
|
455 | exports.runCloudCode = function(rootPath){
|
456 | Global.rootPath = rootPath;
|
457 |
|
458 | if(!fs.existsSync(Global.rootPath + 'config/global.json'))
|
459 | throw "Cloud not find config/global.json";
|
460 | if(!fs.existsSync(Global.rootPath + 'cloud/main.js'))
|
461 | throw "Cloud not find config/global.json";
|
462 |
|
463 | var globalJSON = fs.readFileSync(Global.rootPath + 'config/global.json', 'utf-8')
|
464 | var data = JSON.parse(globalJSON);
|
465 | AV.initialize(data.applicationId, data.applicationKey);
|
466 |
|
467 |
|
468 | var cloudPath = path.resolve(Global.rootPath + 'cloud/main.js');
|
469 | requireFromFile(cloudPath, 'cloud/main.js');
|
470 |
|
471 | var app = Global.app;
|
472 | if(!app){
|
473 | app = express();
|
474 | app.use(express.bodyParser());
|
475 | }
|
476 | var port = app.port || 3000;
|
477 | process.on('uncaughtException', function (err) {
|
478 | var msg = err;
|
479 | var stack = null;
|
480 | if(err.message){
|
481 | msg = err.message;
|
482 | }
|
483 | if(err.stack){
|
484 | stack = err.stack;
|
485 | }
|
486 | console.error((new Date).toUTCString() + ' uncaughtException:', msg);
|
487 | console.error(stack);
|
488 | });
|
489 | addSystemEndpoints(app);
|
490 | app.__listen(port, function() {
|
491 | return console.log("Mock Server is listening on " + port + "\nPress CTRL-C to stop server.");
|
492 | });
|
493 | }
|