motion
Version:
motion - moving development forward
204 lines (183 loc) • 4.81 kB
JavaScript
//
// (C) 2011, Nodejitsu Inc.
// MIT License
//
// A simple web service for storing JSON data via REST
//
// GET - View Object
// POST - Create Object
// PUT - Update Object
// DELETE - Delete Object
//
var revalidator = require('../'),
http = require('http'),
//
// Keep our objects in a simple memory store
//
memoryStore = {},
//
// Set up our request schema
//
schema = {
properties: {
url: {
description: 'the url the object should be stored at',
type: 'string',
pattern: '^/[^#%&*{}\\:<>?\/+]+$',
required: true
},
challenge: {
description: 'a means of protecting data (insufficient for production, used as example)',
type: 'string',
minLength: 5
},
body: {
description: 'what to store at the url',
type: 'any',
default: null
}
}
}
var server = http.createServer(function validateRestRequest (req, res) {
req.method = req.method.toUpperCase();
//
// Log the requests
//
console.log(req.method, req.url);
//
// Buffer the request so it can be parsed as JSON
//
var requestBody = [];
req.on('data', function addDataToBody (data) {
requestBody.push(data);
});
//
// Once the request has ended work with the body
//
req.on('end', function dealWithRest () {
//
// Parse the JSON
//
requestBody = requestBody.join('');
if ({POST: 1, PUT: 1}[req.method]) {
try {
requestBody = JSON.parse(requestBody);
}
catch (e) {
res.writeHead(400);
res.end(e);
return;
}
}
else {
requestBody = {};
}
//
// If this was sent to a url but the body url was not declared
// Make sure the body get the requested url so that our schema
// validates before we work on it
//
if (!requestBody.url) {
requestBody.url = req.url;
}
//
// Don't let users override the main API endpoint
//
if (requestBody.url === '/') {
res.writeHead(400);
res.end('Cannot override the API endpoint "/"');
return;
}
//
// See if our request and target are out of sync
// This lets us double check the url we are about to take up
// if we choose to send the request to the url directly
//
if (req.url !== '/' && requestBody.url !== req.url) {
res.writeHead(400);
res.end('Requested url and actual url do not match');
return;
}
//
// Validate the schema
//
var validation = revalidator.validate(requestBody, schema);
if (!validation.valid) {
res.writeHead(400);
res.end(validation.errors.join('\n'));
return;
}
//
// Grab the current value from storage and
// check if it is a valid state for REST
//
var storedValue = memoryStore[requestBody.url];
if (req.method === 'POST') {
if (storedValue) {
res.writeHead(400);
res.end('ALREADY EXISTS');
return;
}
}
else if (!storedValue) {
res.writeHead(404);
res.end('DOES NOT EXIST');
return;
}
//
// Check our challenge
//
if (storedValue && requestBody.challenge != storedValue.challenge) {
res.writeHead(403);
res.end('NOT AUTHORIZED');
return;
}
//
// Since revalidator only checks and does not manipulate
// our object we need to set up the defaults our selves
// For an easier solution to this please look at Flatiron's
// `Resourceful` project
//
if (requestBody.body === undefined) {
requestBody.body = schema.properties.body.default;
}
//
// Use REST to determine how to manipulate the stored
// values
//
switch (req.method) {
case "GET":
res.writeHead(200);
var result = storedValue.body;
res.end(JSON.stringify(result));
return;
case "POST":
res.writeHead(201);
res.end();
memoryStore[requestBody.url] = requestBody;
return;
case "DELETE":
delete memoryStore[requestBody.url];
res.writeHead(200);
res.end();
return;
case "PUT":
memoryStore[requestBody.url] = requestBody;
res.writeHead(200);
res.end();
return;
default:
res.writeHead(400);
res.end('Invalid Http Verb');
return;
}
});
})
//
// Listen to various ports depending on environment we are being run on
//
server.listen(process.env.PORT || process.env.C9_PORT || 1337, function reportListening () {
console.log('JSON REST Service listening on port', this.address().port);
console.log('Requests can be sent via REST to "/" if they conform to the following schema:');
console.log(JSON.stringify(schema, null, ' '));
});