Permalink
Browse files

update readme; add comments; add example

  • Loading branch information...
1 parent dcaed7c commit ffc59303905d507e7d5813300da8b7b94bfa871b @omphalos committed Jul 31, 2012
Showing with 224 additions and 24 deletions.
  1. +61 −3 README.md
  2. +2 −2 bin/crud-file-server
  3. +37 −18 crud-file-server.js
  4. +123 −0 example/example.html
  5. +1 −1 package.json
View
@@ -1,4 +1,62 @@
-crud-file-server
-================
+Basic file server supporting create, read, update, &a delete
+
+This package exposes a directory to create, read, update, delete operations.
+
+Command-line usage
+==================
+
+ crud-file-server [options]
+
+This starts a file server server using the specified command-line options.
+
+ -f: file system path to expose over http
+ -p: port to listen on (example, 85)
+ -q: suppress the help message
+
+Quickstart
+==========
+
+ npm install crud-file-server
+ crud-file-server -p 85
+
+Now, navigate to http://localhost:85 and you should see the contents of the current directory as a JSON array.
+
+Server-Side Usage
+=================
+
+ require('http-proxy').createServer(function (req, res, proxy) {
+ require('subproxy').handleRequest(subProxyHost, port, req, res, proxy);
+ }).listen(port);
+
+Supported operations
+====================
+
+**GET** returns a file's contents with the correct mime type, or else the contents of a directory as a JSON array.
+**PUT** can be used to write a file.
+**DELETE** can be used to delete a file or folder.
+**POST** supports two operations, rename and create directory.
+For example, POST http://localhost/newDir?create=directory would create a directory named newDir.
+POST http://localhost/new-directory?create=directory would create a new directory named new-directory.
+
+Run the Example
+===============
+
+For further clarification, try running the example:
+
+ npm install crud-file-server
+
+Navigate to the example directory (which should now be under node_modules/crud-file-server/example).
+
+ cd node_modules/crud-file-server/example
+
+Run crud-file-server to host this directory.
+
+ crud-file-server -p 3300
+
+Now use your browser to navigate to http://localhost:3300/example.html.
+You will see a simple client that lets you interact with your file system from the web browser.
+
+
+
+
-basic file server supporting create, read, update, & delete
@@ -7,7 +7,7 @@ if(!argv.q) {
console.log('usage:');
console.log(' crud-file-server [options]');
console.log('');
- console.log('this starts a file server server using the specified command-line options.');
+ console.log('this starts a file server using the specified command-line options');
console.log('');
console.log('options:');
console.log('');
@@ -18,7 +18,7 @@ if(!argv.q) {
}
var port = argv.p || 80;
-var path = argv.f || __dirname;
+var path = argv.f || process.cwd();
require('http').createServer(function (req, res) {
server.handleRequest(port, path, req, res);
View
@@ -1,46 +1,58 @@
var http = require("http");
var fs = require('fs');
+// don't let users crawl up the folder structure by using a/../../../c/d
var cleanUrl = function(url) {
- while(url.indexOf('.').length > 0) { url = url.replace('.', ''); }
+ while(url.indexOf('..').length > 0) { url = url.replace('..', ''); }
return url;
};
-exports.handleRequest = function(port, path, req, res) {
+/*
+example usage:
+ require('http').createServer(function (req, res) {
+ server.handleRequest(port, path, req, res);
+ }).listen(port);
+*/
+exports.handleRequest = function(port, path, req, res) {
+
+ // our error handler
var writeError = function (err, code) {
- console.log('writeError-->');
console.log('err=' + err);
console.log('code=' + code);
code = code || 500;
console.log('code1=' + code);
console.log('Error ' + code + ': ' + err);
- try {
+ // write the error to the response, if possible
+ try {
res.statusCode = code;
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify(err));
} catch(resErr) {
console.log('failed to write error to response: ' + resErr);
}
- console.log('writeError<--');
};
+ if(path.lastIndexOf('/') !== path.length - 1) { path += '/'; } // make sure path ends with a slash
var parsedUrl = require('url').parse(req.url);
var query = query ? {} : require('querystring').parse(parsedUrl.query);
- var url = cleanUrl(parsedUrl.pathname);
+ var url = cleanUrl(parsedUrl.pathname);
+ // normalize the url such that there is no trailing or leading slash /
if(url.lastIndexOf('/') === url.length - 1) { url = url.slice(0, url.length ); }
if(url[0] === '/') { url = url.slice(1, url.length); }
+
console.log(req.method + ' ' + req.url);
var relativePath = path + url;
try {
switch(req.method) {
- case 'GET':
+ case 'GET': // returns file or directory contents
if(url === 'favicon.ico') {
- res.end();
+ res.end(); // if the browser requests favicon, just return an empty response
} else {
- fs.stat(relativePath, function(err, stats) {
+ fs.stat(relativePath, function(err, stats) { // determine if the resource is a file or directory
if(err) { writeError(err); }
else {
if(stats.isDirectory()) {
+ // if it's a directory, return the files as a JSONified array
console.log('reading directory ' + relativePath);
fs.readdir(relativePath, function(err, files) {
if(err) { writeError(err); }
@@ -50,6 +62,7 @@ exports.handleRequest = function(port, path, req, res) {
}
});
} else {
+ // if it's a file, return the contents of a file with the correct content type
console.log('reading file ' + relativePath);
var type = require('mime').lookup(relativePath);
res.setHeader('Content-Type', type);
@@ -64,7 +77,7 @@ exports.handleRequest = function(port, path, req, res) {
});
}
return;
- case 'PUT':
+ case 'PUT': // write a file
console.log('writing ' + relativePath);
var stream = fs.createWriteStream(relativePath);
stream.ok = true;
@@ -79,8 +92,9 @@ exports.handleRequest = function(port, path, req, res) {
writeError(err);
});
return;
- case 'POST':
- if(query.rename) {
+ case 'POST': // create a directory or rename a file or directory
+ if(query.rename) { // rename a file or directory
+ // e.g., http://localhost/old-name.html?rename=new-name.html
query.rename = cleanUrl(query.rename);
console.log('renaming ' + relativePath + ' to ' + path + query.rename);
fs.rename(relativePath, path + query.rename, function(err) {
@@ -89,7 +103,8 @@ exports.handleRequest = function(port, path, req, res) {
res.end();
}
});
- } else if(query.create == 'directory') {
+ } else if(query.create == 'directory') { // rename a directory
+ // e.g., http://localhost/new-directory?create=directory
console.log('creating directory ' + relativePath);
fs.mkdir(relativePath, 0777, function(err) {
if(err) { writeError(err); }
@@ -98,20 +113,20 @@ exports.handleRequest = function(port, path, req, res) {
}
});
} else {
- writeError('valid queries are ' + url + '?rename or ' + url + '?create=directory');
+ writeError('valid queries are ' + url + '?rename=[new name] or ' + url + '?create=directory');
}
return;
- case 'DELETE':
+ case 'DELETE': // delete a file or directory
fs.stat(relativePath, function(err, stats) {
if(err) { writeError(err); }
else {
- if(stats.isDirectory()) {
+ if(stats.isDirectory()) { // delete a directory
console.log('deleting directory ' + relativePath);
fs.rmdir(relativePath, function(err) {
if(err) { writeError(err); }
else { res.end(); }
});
- } else {
+ } else { // delete a file
console.log('deleting file ' + relativePath);
fs.unlink(relativePath, function(err) {
if(err) { writeError(err); }
@@ -121,11 +136,15 @@ exports.handleRequest = function(port, path, req, res) {
}
});
return;
- default:
+ default: // unsupported method! tell the client ...
writeError('Method ' + method + ' not allowed', 405);
return;
}
} catch(err) {
+ // file system ('fs') errors are just bubbled up to this error handler
+ // for example, if the GET is called on a non-existent file, an error will be thrown
+ // and caught here
+ // writeError will write the error information to the response
writeError('unhandled error: ' + err);
}
};
View
@@ -0,0 +1,123 @@
+<html>
+ <head>
+ <title>crud-file-server example</title>
+ <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js" ></script>
+ </head>
+ <body>
+ <fieldset>
+ <p><strong>Read a file or directory</strong></p>
+ <div>
+ <label for="get" >File or directory name:</label>
+ <input type="text" id="get" />
+ </div>
+ <input type="button" id="get-btn" value="GET" />
+ </fieldset>
+ <script>
+ $('#get-btn').click(function() {
+ if($('#get').val()) {
+ window.open($('#get').val());
+ }
+ });
+ </script>
+
+ <fieldset>
+ <p><strong>Write a file</strong></p>
+ <label for="write-path" >File path:</label>
+ <input type="text" id="write-path" />
+ <br>
+ <label for="write-contents" >File contents:</label>
+ <input type="text" id="write-contents" />
+ <input type="button" id="write-btn" value="PUT" />
+ <div id="write-msg"></div>
+ </fieldset>
+ <script>
+ $('#write-btn').click(function() {
+ if($('#write-path').val()) {
+ $.ajax({
+ type: 'PUT',
+ url: '/' + $('#write-path').val(),
+ data: $('#write-contents').val(),
+ }).done(function(data) {
+ $('#write-msg').html('<a href="' + $('#write-path').val() + '">success</a>');
+ }).fail(function(err) {
+ $('#write-msg').html('failure ' + JSON.stringify(err));
+ });
+ }
+ });
+ </script>
+
+ <fieldset>
+ <p><strong>Create a directory</strong></p>
+ <label for="create" >New directory name:</label>
+ <input type="text" id="create" />
+ <br>
+ <input type="button" id="create-btn" value="POST (create)" />
+ <div id="create-msg"></div>
+ </fieldset>
+ <script>
+ $('#create-btn').click(function() {
+ if($('#create').val()) {
+ $.ajax({
+ type: 'POST',
+ url: '/' + $('#create').val() + '?create=directory',
+ }).done(function(data) {
+ $('#create-msg').html('<a href="' + $('#create').val() + '">success</a>');
+ }).fail(function(err) {
+ $('#create-msg').html('failure ' + JSON.stringify(err));
+ });
+ }
+ });
+ </script>
+
+ <fieldset>
+ <p><strong>Rename a file or directory</strong></p>
+ <label for="rename-old" >Old name:</label>
+ <input type="text" id="rename-old" />
+ <br>
+ <label for="rename-new" >New name:</label>
+ <input type="text" id="rename-new" />
+ <input type="button" id="rename-btn" value="POST (rename)" />
+ <div id="rename-msg"></div>
+ </fieldset>
+ <script>
+ $('#rename-btn').click(function() {
+ if($('#rename-old').val() && $('#rename-new').val()) {
+ $.ajax({
+ type: 'POST',
+ url: '/' + $('#rename-old').val() + '?rename=/' + $('#rename-new').val()
+ }).done(function(data) {
+ $('#rename-msg').html('<a href="' + $('#rename-new').val() + '">success</a>');
+ }).fail(function(err) {
+ $('#rename-msg').html('failure ' + JSON.stringify(err));
+ });
+ }
+ });
+ </script>
+
+ <fieldset>
+ <p><strong>Delete a file or directory</strong></p>
+ <label for="delete" >File or directory name:</label>
+ <input type="text" id="delete" />
+ <br>
+ <input type="button" id="delete-btn" value="DELETE" />
+ <div id="delete-msg" ></div>
+ </fieldset>
+ <script>
+ $('#delete-btn').click(function() {
+ if($('#delete').val()) {
+ var url = $('#rename-old').val() + '?rename=' + $('#rename-new').val();
+ console.log(url);
+ $.ajax({
+ type: 'DELETE',
+ url: '/' + $('#delete').val()
+ }).done(function(data) {
+ $('#delete-msg').html('success');
+ }).fail(function(err) {
+ $('#delete-msg').html('failure ' + JSON.stringify(err));
+ });
+ }
+ });
+ </script>
+ </body>
+
+</html>
View
@@ -1,6 +1,6 @@
{
"name": "crud-file-server",
- "version": "0.0.2",
+ "version": "0.0.3",
"description": "basic file server supporting create, read, update, & delete",
"bin": {
"crud-file-server": "./bin/crud-file-server"

0 comments on commit ffc5930

Please sign in to comment.