Skip to content

Commit 2309f8b

Browse files
committed
Adds ParseServerRESTController experimental support
1 parent be9fb43 commit 2309f8b

File tree

4 files changed

+144
-39
lines changed

4 files changed

+144
-39
lines changed

src/ParseServer.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ import DatabaseController from './Controllers/DatabaseController';
5858
import SchemaCache from './Controllers/SchemaCache';
5959
import ParsePushAdapter from 'parse-server-push-adapter';
6060
import MongoStorageAdapter from './Adapters/Storage/Mongo/MongoStorageAdapter';
61+
62+
import { ParseServerRESTController } from './ParseServerRESTController';
6163
// Mutate the Parse object to add the Cloud Code handlers
6264
addParseCloud();
6365

@@ -315,6 +317,9 @@ class ParseServer {
315317
}
316318
});
317319
}
320+
if (process.env.PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS === '1') {
321+
Parse.CoreManager.setRESTController(ParseServerRESTController(appId, appRouter));
322+
}
318323
return api;
319324
}
320325

src/ParseServerRESTController.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
const Config = require('./Config');
2+
const Auth = require('./Auth');
3+
const RESTController = require('parse/lib/node/RESTController');
4+
const URL = require('url');
5+
import { logger } from './logger';
6+
7+
export function ParseServerRESTController(applicationId, router) {
8+
function handleRequest(method, path, data = {}, options = {}) {
9+
let args = arguments;
10+
if (path == 'batch') {
11+
let promises = data.requests.map((request) => {
12+
return handleRequest(request.method, request.path, request.body, options).then((response) => {
13+
return Parse.Promise.as({success: response});
14+
}, (error) => {
15+
return Parse.Promise.as({error: {code: error.code, error: error.message}});
16+
});
17+
});
18+
return Parse.Promise.all(promises);
19+
}
20+
21+
let config = new Config(applicationId);
22+
let serverURL = URL.parse(config.serverURL);
23+
if (path.indexOf(serverURL.path) === 0) {
24+
path = path.slice(serverURL.path.length, path.length);
25+
}
26+
27+
if (path[0] !== "/") {
28+
path = "/" + path;
29+
}
30+
31+
function getSessionToken(options) {
32+
if (options && typeof options.sessionToken === 'string') {
33+
return Parse.Promise.as(options.sessionToken);
34+
}
35+
return Parse.Promise.as(null);
36+
}
37+
38+
function getAuth(options, config) {
39+
if (options.useMasterKey) {
40+
return Parse.Promise.as(new Auth.Auth({config, isMaster: true }));
41+
}
42+
return getSessionToken(options).then((sessionToken) => {
43+
if (sessionToken) {
44+
options.sessionToken = sessionToken;
45+
return Auth.getAuthForSessionToken({
46+
config,
47+
sessionToken: sessionToken
48+
});
49+
} else {
50+
return Parse.Promise.as(new Auth.Auth({ config }));
51+
}
52+
})
53+
}
54+
55+
let query;
56+
if (method === 'GET') {
57+
query = data;
58+
}
59+
60+
return new Parse.Promise((resolve, reject) => {
61+
getAuth(options, config).then((auth) => {
62+
let request = {
63+
body: data,
64+
config,
65+
auth,
66+
info: {
67+
applicationId: applicationId,
68+
sessionToken: options.sessionToken
69+
},
70+
query
71+
};
72+
return Promise.resolve().then(() => {
73+
return router.tryRouteRequest(method, path, request);
74+
}).then((response) => {
75+
resolve(response.response, response.status, response);
76+
}, (err) => {
77+
if (err instanceof Parse.Error &&
78+
err.code == Parse.Error.INVALID_JSON &&
79+
err.message == `cannot route ${method} ${path}`) {
80+
RESTController.request.apply(null, args).then(resolve, reject);
81+
} else {
82+
reject(err);
83+
}
84+
});
85+
}, reject);
86+
});
87+
88+
};
89+
90+
return {
91+
request: handleRequest,
92+
ajax: function() {
93+
return RESTController.ajax.apply(null, arguments);
94+
}
95+
};
96+
};

src/PromiseRouter.js

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,22 @@ import express from 'express';
1010
import url from 'url';
1111
import log from './logger';
1212
import {inspect} from 'util';
13+
const Layer = require('express/lib/router/layer');
14+
15+
function validateParameter(key, value) {
16+
if (key == 'className') {
17+
if (value.match(/_?[A-Za-z][A-Za-z_0-9]*/)) {
18+
return value;
19+
}
20+
} else if (key == 'objectId') {
21+
if (value.match(/[A-Za-z0-9]+/)) {
22+
return value;
23+
}
24+
} else {
25+
return value;
26+
}
27+
}
28+
1329

1430
export default class PromiseRouter {
1531
// Each entry should be an object with:
@@ -70,7 +86,8 @@ export default class PromiseRouter {
7086
this.routes.push({
7187
path: path,
7288
method: method,
73-
handler: handler
89+
handler: handler,
90+
layer: new Layer(path, null, handler)
7491
});
7592
};
7693

@@ -83,30 +100,15 @@ export default class PromiseRouter {
83100
if (route.method != method) {
84101
continue;
85102
}
86-
// NOTE: we can only route the specific wildcards :className and
87-
// :objectId, and in that order.
88-
// This is pretty hacky but I don't want to rebuild the entire
89-
// express route matcher. Maybe there's a way to reuse its logic.
90-
var pattern = '^' + route.path + '$';
91-
92-
pattern = pattern.replace(':className',
93-
'(_?[A-Za-z][A-Za-z_0-9]*)');
94-
pattern = pattern.replace(':objectId',
95-
'([A-Za-z0-9]+)');
96-
var re = new RegExp(pattern);
97-
var m = path.match(re);
98-
if (!m) {
99-
continue;
100-
}
101-
var params = {};
102-
if (m[1]) {
103-
params.className = m[1];
104-
}
105-
if (m[2]) {
106-
params.objectId = m[2];
103+
let layer = route.layer || new Layer(route.path, null, route.handler);
104+
let match = layer.match(path);
105+
if (match) {
106+
let params = layer.params;
107+
Object.keys(params).forEach((key) => {
108+
params[key] = validateParameter(key, params[key]);
109+
});
110+
return {params: params, handler: route.handler};
107111
}
108-
109-
return {params: params, handler: route.handler};
110112
}
111113
};
112114

@@ -124,6 +126,19 @@ export default class PromiseRouter {
124126
expressRouter() {
125127
return this.mountOnto(express.Router());
126128
}
129+
130+
tryRouteRequest(method, path, request) {
131+
var match = this.match(method, path);
132+
if (!match) {
133+
throw new Parse.Error(
134+
Parse.Error.INVALID_JSON,
135+
'cannot route ' + method + ' ' + path);
136+
}
137+
request.params = match.params;
138+
return new Promise((resolve, reject) => {
139+
match.handler(request).then(resolve, reject);
140+
});
141+
}
127142
}
128143

129144
// A helper function to make an express handler out of a a promise

src/batch.js

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,39 +29,28 @@ function handleBatch(router, req) {
2929
var apiPrefixLength = req.originalUrl.length - batchPath.length;
3030
var apiPrefix = req.originalUrl.slice(0, apiPrefixLength);
3131

32-
var promises = [];
33-
for (var restRequest of req.body.requests) {
32+
const promises = req.body.requests.map((restRequest) => {
3433
// The routablePath is the path minus the api prefix
3534
if (restRequest.path.slice(0, apiPrefixLength) != apiPrefix) {
3635
throw new Parse.Error(
3736
Parse.Error.INVALID_JSON,
3837
'cannot route batch path ' + restRequest.path);
3938
}
4039
var routablePath = restRequest.path.slice(apiPrefixLength);
41-
42-
// Use the router to figure out what handler to use
43-
var match = router.match(restRequest.method, routablePath);
44-
if (!match) {
45-
throw new Parse.Error(
46-
Parse.Error.INVALID_JSON,
47-
'cannot route ' + restRequest.method + ' ' + routablePath);
48-
}
49-
5040
// Construct a request that we can send to a handler
5141
var request = {
5242
body: restRequest.body,
53-
params: match.params,
5443
config: req.config,
5544
auth: req.auth,
5645
info: req.info
5746
};
5847

59-
promises.push(match.handler(request).then((response) => {
48+
return router.tryRouteRequest(restRequest.method, routablePath, request).then((response) => {
6049
return {success: response.response};
6150
}, (error) => {
6251
return {error: {code: error.code, error: error.message}};
63-
}));
64-
}
52+
});
53+
});
6554

6655
return Promise.all(promises).then((results) => {
6756
return {response: results};

0 commit comments

Comments
 (0)