Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

V0.3

  • Loading branch information...
commit 2151b43704b15efa0cda8c5c68b94d85cd756248 0 parents
@mrohad authored
Showing with 2,795 additions and 0 deletions.
  1. +17 −0 .project
  2. +6 −0 .settings/.jsdtscope
  3. +1 −0  .settings/org.eclipse.wst.jsdt.ui.superType.container
  4. +1 −0  .settings/org.eclipse.wst.jsdt.ui.superType.name
  5. BIN  AlligatorV029.tar.gz
  6. +33 −0 ChangeLog
  7. +21 −0 LICENSE
  8. +121 −0 README.markdown
  9. +21 −0 WWW/a.jssp
  10. +13 −0 WWW/bla.html
  11. +29 −0 WWW/bla.jssp
  12. +28 −0 WWW/counter.js
  13. +27 −0 WWW/counter.jssp
  14. +3 −0  WWW/error.js
  15. +2 −0  WWW/error.jssp
  16. BIN  WWW/favicon.ico
  17. +3 −0  WWW/forward.jssp
  18. +38 −0 WWW/hello.js
  19. +38 −0 WWW/hello.jssp
  20. +4 −0 WWW/post.html
  21. +3 −0  WWW/redirect.jssp
  22. +3 −0  WWW/session.jssp
  23. +4 −0 WWW/sessionGet.jssp
  24. +3 −0  WWW/sessionPut.jssp
  25. +13 −0 WWW/test.js
  26. +13 −0 WWW/test.jssp
  27. +10 −0 WWWlib/test.js
  28. +21 −0 alligator.js
  29. +5 −0 futureFeatures.txt
  30. +4 −0 knowBugs.txt
  31. +107 −0 lib/application.js
  32. +41 −0 lib/asemaphore.js
  33. +352 −0 lib/content-type.js
  34. +341 −0 lib/incoming_form.js
  35. +401 −0 lib/jssp.js
  36. +33 −0 lib/log.js
  37. +81 −0 lib/mc/connection.js
  38. +114 −0 lib/mc/memcache.js
  39. +72 −0 lib/mc/pool.js
  40. +52 −0 lib/mc/request.js
  41. +176 −0 lib/multinode/multi-node.js
  42. +313 −0 lib/multipart_parser.js
  43. +25 −0 lib/querystring_parser.js
  44. +118 −0 lib/session.js
  45. +33 −0 lib/utils.js
  46. +29 −0 package.json
  47. +22 −0 settings.json
17 .project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>Alligator</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.wst.jsdt.core.javascriptValidator</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.wst.jsdt.core.jsNature</nature>
+ </natures>
+</projectDescription>
6 .settings/.jsdtscope
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="src" path=""/>
+ <classpathentry kind="con" path="org.eclipse.wst.jsdt.launching.JRE_CONTAINER"/>
+ <classpathentry kind="output" path=""/>
+</classpath>
1  .settings/org.eclipse.wst.jsdt.ui.superType.container
@@ -0,0 +1 @@
+org.eclipse.wst.jsdt.launching.JRE_CONTAINER
1  .settings/org.eclipse.wst.jsdt.ui.superType.name
@@ -0,0 +1 @@
+Global
BIN  AlligatorV029.tar.gz
Binary file not shown
33 ChangeLog
@@ -0,0 +1,33 @@
+Change LOG 0.3
+================================
+Features:
+1) Supports multi-node with session-scope and application-scope utilizing memcached.
+
+BugFixes
+1) bugs with static files support
+
+
+Change LOG 0.29
+================================
+Features:
+1) Compiling each JSSP file to JS file, upon a second use it evals/requires(the js file).
+
+BugFixes
+1) forward() is now working again
+
+Change LOG 0.28
+================================
+Features:
+1) Error details, on debug_mode Alligator generates a file by the name debugging.js, and add the error line to the response.
+
+
+
+
+
+Change LOG 0.27
+================================
+Features:
+1) multinode support - no shared memory yet
+
+BugFixes
+1) One can now write a server-side script with comments (//)
21 LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) 2010 Noah Sloan <http://noahsloan.com>
+and Mark Hansen <http://markhansen.co.nz>
+and Ohad Assulin
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
121 README.markdown
@@ -0,0 +1,121 @@
+Alligator is a simple application server built on top of AntiNode and Node.js
+
+Latest Version 0.3
+
+# Usage
+
+Run it from the command line.
+
+ $ node alligator.js
+
+Requires Node.JS v0.1.99 or greater.
+If you want to bind to a port under 1024, you'll need to run node with special
+privileges.
+
+# Configuration
+
+Configuration is through a JSON text file - `settings.json` in
+the same folder as `alligator.js`.
+
+Example settings file:
+
+ {
+ "web_app_name" : "Alligator TestApp",
+ "port" : 82,
+ "path" : {
+ "root":"/home/vadmin/Alligator/WWW/",
+ "lib":"/home/vadmin/Alligator/WWWlib/"
+ },
+ "server_script": {
+ "ext":"jssp",
+ "begin":"<?",
+ "begin_additional_write":"=",
+ "end":"?>",
+ "session_minutes":1,
+ "memcached":{
+ "enable":0,
+ "server":"localhost",
+ "port":11211
+ }
+ },
+ "debug_mode" : 1,
+ "nodes" : 2
+ }
+
+This server listens on port 8080 for HTTP requests.
+
+Explanation of properties:
+
+- `web_app_name` - the name of the current web application
+- `port` - the server listen to this port (default = 80)
+- `path.root` - the root folder for the static and dynamic files (default = WWW)
+- `path.lib` - the folder for JavaScript files that will get loaded automatically - you can access those via lib.fileName.yourStuff (default = None)
+- `server_script.ext` - the extension of files that contains of server-side script (default = jssp)
+- `server_script.begin` - the beginning tag for server-side scripting (default = <?)
+- `server_script.end` - the end tag for server-side scripting (default = ?>)
+- `server_script.session_minutes` - session timeout after X minutes (default = 30)
+- `server_script.memcached` - integration with memcached (Under construction)
+- `debug_mode` - 1 for debug mode, 0 for non-debug mode, on debug mode we add the exception to the response + the log level = debug
+- `nodes` - number of process running this application server( currently we don't support shared memory - under construction)
+
+
+
+# Features
+- Handling request parameters easily (for GET and POST)
+- Session management, including timeout
+- Application scope management
+- Dynamic js loader (the lib folder)
+- User can set the server-side script begin and end tags
+
+When one is writing a script nested to the <? some;JavaScript;Code;In Here;?> tags
+
+He/She can use the following implicit functions/variables:
+
+- `request` for handling the request
+- `request.parameters` to read and write request post/get parameters
+- `responseHead.header` to add headers of the response (e.g. responseHead.header["set-cookie"] = "..";)
+- `responseHead.status` to change the HTTP response status
+- `write(str)` to add string to the response body (or instead one can use the <?=str?> tag)
+- `forward(other.jssp)` to forward the request and response to another server-side resource
+- `sendRedirect(url)` to send HTTP redirect response back to the client
+- `lib.filename.member` to access whatever lib one has loaded
+- `session.put(key,value)` to put anything on the HttpSession
+- `session.get(key)` to get anything from the HttpSession
+- `application.someparam` to get someParam from the application scope
+
+
+
+#Examples
+Ex1.jssp:
+ <? var a = 1+1;?><br/>
+ <? write(a);?>
+Translates to:
+ <br/>2
+Once can achieve the same thing using Ex2.jssp
+ <? var a = 1+1;?><br/>
+ <?=a?>
+
+Once can forward from one jssp to another:
+Exf1.jssp:
+ <? var dbInfo= gettingInfoFromDatabase();
+ request.parameters.db = dbInfo;
+ forward("showTable.jssp");?>
+showTable.jssp:
+ <?=genetrateHTMLTable(request.parameters.db)?>
+
+One can easily redirect:
+ <? if(request.parameters.googleIt=="true")
+ sendRedirect("http://www.google.com");
+ else{>
+ <H1> Welcome...</H1>
+ <?}?>
+
+# Bugs and Contribution
+Please let me know if you find any bug or if you would like to contribute code: mrohad.jsf at gmail
+
+Known Bugs - http://github.com/mrohad/Alligator/blob/master/knowBugs.txt
+
+# Credits
+
+Original code forked from AntiNode @ http://github.com/mhansen/antinode
+We are using Multi-node @ http://github.com/kriszyp/multi-node
21 WWW/a.jssp
@@ -0,0 +1,21 @@
+<HTML>
+
+ <BODY>
+ <?
+ if(application.counter == undefined)
+ application.counter=1;
+ else{
+ application.counter+=1;
+ }
+ var a = 5;
+ a +=4;aasas
+ a+=2;
+ ?>
+ HELLO WORLD<br/>
+ Number of users:
+ <?write(application.counter);?>
+ <br/>
+ <?write(a);?>
+
+ </BODY>
+</HTML>
13 WWW/bla.html
@@ -0,0 +1,13 @@
+<HTML>
+ <?
+ var a = 1;
+ add("<head><title>nodeJS</title></head>");
+ ?>
+ <BODY>
+ HELLO WORLD
+ <?
+ ss(D);
+ add(a);
+ ?>
+ </BODY>
+</HTML>
29 WWW/bla.jssp
@@ -0,0 +1,29 @@
+<HTML>
+ <?
+ write("<head><title>nodeJS</title></head>");
+ ?>
+ <BODY>
+ <?
+ if(application.counter == undefined)
+ application.counter=1;
+ else{
+ application.counter+=1;
+ write(lib.test.abc);
+ }
+ ?>
+ HELLO WORLD<br/>
+ Number of users:
+ <?
+ write(application.counter+"<br/>");
+ if(request.parameters.asas != undefined && request.parameters.asas=="1"){
+ ?>
+ YES!
+ <?}?>
+ <?for (index=0;index<5;index++){ write(index);?>;<? }?>
+ <br/>
+ By Ohad a
+ <?
+ session.put("a",1);
+ ?>
+ </BODY>
+</HTML>
28 WWW/counter.js
@@ -0,0 +1,28 @@
+(function(log,lib,application,request,responseHead,commands,session) {commands.writeEscapedText("%3CHTML%3E%0A");
+commands.writeEscapedText("%09%09");commands.write("<head><title>Application Scope Counter Tester</title></head>");
+commands.writeEscapedText("%09%3CBODY%3E%0A");
+commands.writeEscapedText("%09%09");
+ var counter = 1;
+ application.get("counter",function(value){
+ log.debug("COUNTER.JSSP, value - " +value);
+ if(value == undefined){
+ application.set("counter",1);
+
+commands.writeEscapedText("%09%09First%20Time%3B%0A");
+commands.writeEscapedText("%09%09%09%09%09%0A");
+commands.writeEscapedText("%09%09%09%09");}else{
+ counter = value+1;
+ application.set("counter",counter);
+
+commands.writeEscapedText("%09%09%0A");
+commands.writeEscapedText("%09%09Number%20of%20users%20%3A%20");commands.write(counter);
+commands.writeEscapedText("%09%09%09%09%0A");
+commands.writeEscapedText("%09%09");}
+commands.writeEscapedText("%09%09%0A");
+commands.writeEscapedText("%09%09%3C/BODY%3E%0A");
+commands.writeEscapedText("%3C/HTML%3E%0A");
+commands.writeEscapedText("%09%09%09%09%09%0A");
+commands.writeEscapedText("%09%09%09%09%0A");
+commands.writeEscapedText("%09%09%09");});
+
+})
27 WWW/counter.jssp
@@ -0,0 +1,27 @@
+<HTML>
+ <?="<head><title>Application Scope Counter Tester</title></head>"?>
+ <BODY>
+ <?
+ var counter = 1;
+ application.get("counter",function(value){
+ log.debug("COUNTER.JSSP, value - " +value);
+ if(value == undefined){
+ application.set("counter",1);
+ ?>
+ First Time;
+
+ <?}else{
+ counter = value+1;
+ application.set("counter",counter);
+ ?>
+
+ Number of users : <?=counter?>
+
+ <?}?>
+
+ </BODY>
+</HTML>
+
+
+ <?});
+ ?>
3  WWW/error.js
@@ -0,0 +1,3 @@
+exports.run = (function(lib,application,request,responseHead,writeEscapedText,forward,sendRedirect,write,session) {writeEscapedText("ERROR%3A%0A");
+writeEscapedText(""); write(;
+})
2  WWW/error.jssp
@@ -0,0 +1,2 @@
+ERROR:
+<? write(; ?>
BIN  WWW/favicon.ico
Binary file not shown
3  WWW/forward.jssp
@@ -0,0 +1,3 @@
+<?
+ forward("hello.jssp");
+?>
38 WWW/hello.js
@@ -0,0 +1,38 @@
+(function(log,lib,application,request,responseHead,writeEscapedText,forward,sendRedirect,write,session) {writeEscapedText("%3CHTML%3E%0A");
+writeEscapedText("%09%09");write("<head><title>nodeJS and antiNode+</title></head>");
+writeEscapedText("%09%3CBODY%3E%0A");
+writeEscapedText("%09%09");
+ var counter = 1;
+ application.get("counter",function(value){
+ log.debug("HELLO.JSSP, value - " +value);
+ if(value == undefined)
+ application.set("counter",1);
+ else{
+ counter = value+1;
+ application.set("counter",counter);
+ }
+ });
+
+writeEscapedText("%09%09HELLO%20WORLD%3Cbr/%3E%0A");
+writeEscapedText("%09%09Number%20of%20users%3A%0A");
+writeEscapedText("%09%09");
+ write(counter+"<br/>");
+
+writeEscapedText("%09%09%09%3Cbr/%3Erequest.parameters.night%3A%20");write(request.parameters.night);writeEscapedText("%3C/br%3E%0A");
+writeEscapedText("%09%09"); if(request.parameters.night != undefined && request.parameters.night=="1"){
+
+writeEscapedText("%09%09Good%20night%21%20%0A");
+writeEscapedText("%09%09");}else{
+writeEscapedText("%09%09Good%20Day%21%0A");
+writeEscapedText("%09%09");}
+writeEscapedText("%09%09%3Cbr/%3E%0A");
+writeEscapedText("%09%09");for (index=0;index<5;index++){ write(index);writeEscapedText("%3B"); }
+writeEscapedText("%09%09%3Cbr/%3E%0A");
+writeEscapedText("%09%09Testing%20the%20lib%3A%09%09%3Cbr/%3E%0A");
+writeEscapedText("%09%09Request%20headers%3A%20");write(lib.test.arrayToString(request.headers));writeEscapedText("%3C/br%3E%0A");
+writeEscapedText("%09%09%3Cbr/%3E%0A");
+writeEscapedText("%09%09Setting%20a%20cookie%20a%3Db%20using%3A%20responseHead.headers%5B%22Set-cookie%22%5D%20%3D%20%22a%3Db%22%3B%3Cbr/%3E%0A");
+writeEscapedText("%09%09");responseHead.headers["Set-cookie"] = "a=b";
+writeEscapedText("%09%3C/BODY%3E%0A");
+writeEscapedText("%3C/HTML%3E%0A");
+})
38 WWW/hello.jssp
@@ -0,0 +1,38 @@
+<HTML>
+ <?="<head><title>nodeJS and antiNode+</title></head>"?>
+ <BODY>
+ <?
+ var counter = 1;
+ application.get("counter",function(value){
+ log.debug("HELLO.JSSP, value - " +value);
+ if(value == undefined)
+ application.set("counter",1);
+ else{
+ counter = value+1;
+ application.set("counter",counter);
+ }
+ });
+ ?>
+ HELLO WORLD<br/>
+ Number of users:
+ <?
+ write(counter+"<br/>");
+ ?>
+ <br/>request.parameters.night: <?=request.parameters.night?></br>
+ <? if(request.parameters.night != undefined && request.parameters.night=="1"){
+ ?>
+ Good night!
+ <?}else{?>
+ Good Day!
+ <?}?>
+ <br/>
+ <?for (index=0;index<5;index++){ commands.write(index);?>;<? }?>
+ <br/>
+ Testing the lib: <br/>
+ Request headers: <?=lib.test.arrayToString(request.headers)?></br>
+ <br/>
+ Setting a cookie a=b using: responseHead.headers["Set-cookie"] = "a=b";<br/>
+ <?responseHead.headers["Set-cookie"] = "a=b";?>
+
+ </BODY>
+</HTML>
4 WWW/post.html
@@ -0,0 +1,4 @@
+<form action="hello.jssp" method="post">
+ <input name="night"> <input name="b">
+ <input type="submit">
+</form>
3  WWW/redirect.jssp
@@ -0,0 +1,3 @@
+<?
+ sendRedirect("http://www.google.com");
+?>
3  WWW/session.jssp
@@ -0,0 +1,3 @@
+<?
+ write(session.get("a"));
+?>
4 WWW/sessionGet.jssp
@@ -0,0 +1,4 @@
+<?
+ write(session.get("x"));
+
+?>
3  WWW/sessionPut.jssp
@@ -0,0 +1,3 @@
+<?
+ session.put("x","aaa");
+?>
13 WWW/test.js
@@ -0,0 +1,13 @@
+(function(lib,application,request,responseHead,writeEscapedText,forward,sendRedirect,write,session) {writeEscapedText("%3CHTML%3E%0A");
+writeEscapedText("%09%09%3Chead%3E%3Ctitle%3EAlligatro%20test%3C/title%3E%3C/head%3E%0A");
+writeEscapedText("%09%3CBODY%3E%0A");
+writeEscapedText("%09%09"); if(request.parameters.night != undefined && request.parameters.night=="1"){
+writeEscapedText("%09%09%09Good%20night%21%20%0A");
+writeEscapedText("%09%09");}else{
+writeEscapedText("%09%09%09Hello%20World%21%0A");
+writeEscapedText("%09%09");}
+writeEscapedText("%09%0A");
+writeEscapedText("%09%3Cbr/%3E%0A");
+writeEscapedText("%09%3C/BODY%3E%0A");
+writeEscapedText("%3C/HTML%3E%0A");
+})
13 WWW/test.jssp
@@ -0,0 +1,13 @@
+<HTML>
+ <head><title>Alligatro test</title></head>
+ <BODY>
+
+ <? if(request.parameters.night != undefined && request.parameters.night=="1"){?>
+ Good night!
+ <?}else{?>
+ Hello World!
+ <?}?>
+
+ <br/>
+ </BODY>
+</HTML>
10 WWWlib/test.js
@@ -0,0 +1,10 @@
+sys = require('sys');
+
+exports.arrayToString = function(arr){
+ var output = "";
+ for (property in arr)
+ output += property + ': ' + arr[property]+'; ';
+ return output;
+
+}
+
21 alligator.js
@@ -0,0 +1,21 @@
+var fs = require('fs'),
+jssp = require('./lib/jssp'),
+sys = require('sys');
+
+fs.readFile(process.argv[2] || './settings.json', function(err, data) {
+ var settings = {};
+ if (err) {
+ sys.puts('No settings.json found ('+err+'). Using default settings');
+ } else {
+ try {
+ settings = JSON.parse(data.toString('utf8',0,data.length));
+ } catch (e) {
+ sys.puts('Error parsing settings.json: '+e);
+ process.exit(1);
+ }
+ }
+ jssp.start(settings);
+});
+
+
+
5 futureFeatures.txt
@@ -0,0 +1,5 @@
+1) enableing multi-node using memcahced
+2) require instead of eval (when loading compiled jssp file)
+3) enabling websockets variables
+4) enableing 2-sides tag (script that runs on both server and client side)
+
4 knowBugs.txt
@@ -0,0 +1,4 @@
+1) Currently using eval instead of require (http://groups.google.com/group/nodejs/browse_thread/thread/7a2e409ec970198e)
+2) Crash when memcached crash
+
+
107 lib/application.js
@@ -0,0 +1,107 @@
+var memcache = require('./mc/memcache'),
+asemaphore = require('./asemaphore'),
+log = require('./log');
+
+
+function applicationScope(webAppName,mcOptions){
+ this.webAppName = webAppName.replace(" ","");
+ this.mcOptions = mcOptions;
+
+ this.get = function(seprator,key,cb){
+ if(this.mcOptions.enable){
+ var connection = new Memcache(this.mcOptions.server, this.mcOptions.port);
+ log.debug("connectin memcache for get key("+seprator+this.webAppName+key+")");
+ connection.get(seprator+this.webAppName+key, function(response) {
+ log.debug("Memecahe GET results("+response.success+") - "+response.data);
+ if (cb){
+ if(response.success)
+ try{
+
+ cb(JSON.parse(response.data));
+ }
+ catch(err){
+ cb(undefined);
+ }
+ else
+ cb(undefined);
+ }
+ });
+ }else
+ if (cb) cb(this[seprator+this.webAppName+key]);
+ };
+
+ this.set = function(seprator,key,value,cb){
+ if(this.mcOptions.enable){
+ var connection = new Memcache(this.mcOptions.server, this.mcOptions.port);
+ log.debug("connectin memcache for get key("+seprator+this.webAppName+key+")");
+ connection.set(seprator+this.webAppName+key, JSON.stringify(value), {expires:0,flags:0,callback: function () {
+ if (cb) cb();
+ }});
+ }else{
+ this[seprator+this.webAppName+key] = value;
+ if (cb) cb();
+ }
+ };
+
+ this.remove = function(seprator,key,cb){
+ if(this.mcOptions.enable){
+ var connection = new Memcache(this.mcOptions.server, this.mcOptions.port);
+ log.debug("connectin memcache for get "+this.mcOptions.server+" " +this.mcOptions.port);
+ connection.del(seprator+this.webAppName+key, {callback: function () {
+ if (cb) cb();
+ }});
+ }else
+ this[seprator+this.webAppName+key] = undefined;
+ if (cb) cb();
+ };
+}
+
+var instance = undefined;
+exports.start = function(webAppName,mcOptions){
+ log.info("Starting application scope for "+webAppName+" memcahecOptions - "+mcOptions);
+ if(instance == undefined)
+ instance = new applicationScope(webAppName,mcOptions);
+ return instance;
+};
+
+function warpCallBackWithAsemaphore(cb,asemaphore){
+ if(!cb)
+ return undefined;
+ else{
+ asemaphore.v();
+ return function(){
+ asemaphore.p();
+ cb.apply(this, Array.prototype.slice.call(arguments, 0));
+ };
+ }
+}
+exports.getManager = function(currentAsemaphore){
+ return {
+ get : function(key,cb){
+ if(instance==undefined)
+ throw "You have to call start() first";
+ if(cb!=undefined)
+ instance.get("", key, asemaphore.warpCallBack(currentAsemaphore,cb));
+ else
+ instance.get("", key, undefined);
+ },
+
+ set : function(key,value,cb){
+ if(instance==undefined)
+ throw "You have to call start() first";
+ if(cb!=undefined)
+ instance.set("", key, value, asemaphore.warpCallBack(currentAsemaphore,cb));
+ else
+ instance.set("", key, value, undefined);
+ },
+
+ remove : function(key,cb){
+ if(instance==undefined)
+ throw "You have to call start() first";
+ if(cb!=undefined)
+ instance.remove(seprator, key, asemaphore.warpCallBack(currentAsemaphore,cb));
+ else
+ instance.remove(seprator, key, undefined);
+ }
+ };
+};
41 lib/asemaphore.js
@@ -0,0 +1,41 @@
+var log = require('./log');
+
+function asemaphore(counter,fireFunc){
+ this.counter = (counter==undefined?0:counter);
+ this.fire = fireFunc;
+ log.debug("Starting Asemaphore with counter:" +this.counter);
+
+
+ this.v = function(){
+ ++this.counter;
+ log.debug("Asemaphore counter after v(): " +this.counter);
+ return this.counter;
+ };
+
+ this.p = function(){
+ if((--this.counter)<1){
+ log.debug("Asemaphore fire() after p()");
+ this.fire();
+ }
+ log.debug("Asemaphore counter after p(): " +this.counter);
+ return this.counter;
+ };
+
+
+}
+
+exports.ctor = function(counter,fireFunc){
+ return new asemaphore(counter,fireFunc);
+};
+
+exports.warpCallBack = function(asemaphore,cb){
+ if(cb == undefined)
+ return undefined;
+ else{
+ asemaphore.v();
+ return function(){
+ cb.apply(this, Array.prototype.slice.call(arguments, 0));
+ asemaphore.p();
+ };
+ }
+};
352 lib/content-type.js
@@ -0,0 +1,352 @@
+p/*
+ * Modified from nerve at http://github.com/gjritter/nerve/blob/master/lib/mime.js
+ * Copyright (c) 2009 Codespin Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+var path = require('path');
+ require('./utils');
+ // map of mime types, originally from Express
+ // modified to work with values returned by require('path').extname(pathname)
+ // Express - Mime - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
+ extToType = {
+ '.323' : 'text/h323',
+ '.3gp' : 'video/3gpp',
+ '.a' : 'application/octet-stream',
+ '.acx' : 'application/internet-property-stream',
+ '.ai' : 'application/postscript',
+ '.aif' : 'audio/x-aiff',
+ '.aifc' : 'audio/x-aiff',
+ '.aiff' : 'audio/x-aiff',
+ '.asc' : 'application/pgp-signature',
+ '.asf' : 'video/x-ms-asf',
+ '.asr' : 'video/x-ms-asf',
+ '.asm' : 'text/x-asm',
+ '.asx' : 'video/x-ms-asf',
+ '.atom' : 'application/atom+xml',
+ '.au' : 'audio/basic',
+ '.avi' : 'video/x-msvideo',
+ '.axs' : 'application/olescript',
+ '.bas' : 'text/plain',
+ '.bat' : 'application/x-msdownload',
+ '.bcpio' : 'application/x-bcpio',
+ '.bin' : 'application/octet-stream',
+ '.bmp' : 'image/bmp',
+ '.bz2' : 'application/x-bzip2',
+ '.c' : 'text/x-c',
+ '.cab' : 'application/vnd.ms-cab-compressed',
+ '.cat' : 'application/vnd.ms-pkiseccat',
+ '.cc' : 'text/x-c',
+ '.cdf' : 'application/x-netcdf',
+ '.cer' : 'application/x-x509-ca-cert',
+ '.cgm' : 'image/cgm',
+ '.chm' : 'application/vnd.ms-htmlhelp',
+ '.class' : 'application/octet-stream',
+ '.clp' : 'application/x-msclip',
+ '.cmx' : 'image/x-cmx',
+ '.cod' : 'image/cis-cod',
+ '.com' : 'application/x-msdownload',
+ '.conf' : 'text/plain',
+ '.cpio' : 'application/x-cpio',
+ '.cpp' : 'text/x-c',
+ '.cpt' : 'application/mac-compactpro',
+ '.crd' : 'application/x-mscardfile',
+ '.crl' : 'application/pkix-crl',
+ '.crt' : 'application/x-x509-ca-cert',
+ '.csh' : 'application/x-csh',
+ '.css' : 'text/css',
+ '.csv' : 'text/csv',
+ '.cxx' : 'text/x-c',
+ '.dcr' : 'application/x-director',
+ '.deb' : 'application/x-debian-package',
+ '.der' : 'application/x-x509-ca-cert',
+ '.diff' : 'text/x-diff',
+ '.dir' : 'application/x-director',
+ '.djv' : 'image/vnd.djvu',
+ '.djvu' : 'image/vnd.djvu',
+ '.dll' : 'application/x-msdownload',
+ '.dmg' : 'application/octet-stream',
+ '.dms' : 'application/octet-stream',
+ '.doc' : 'application/msword',
+ '.dot' : 'application/msword',
+ '.dtd' : 'application/xml-dtd',
+ '.dv' : 'video/x-dv',
+ '.dvi' : 'application/x-dvi',
+ '.dxr' : 'application/x-director',
+ '.ear' : 'application/java-archive',
+ '.eml' : 'message/rfc822',
+ '.eps' : 'application/postscript',
+ '.etx' : 'text/x-setext',
+ '.evy' : 'application/envoy',
+ '.exe' : 'application/x-msdownload',
+ '.ez' : 'application/andrew-inset',
+ '.f' : 'text/x-fortran',
+ '.f77' : 'text/x-fortran',
+ '.f90' : 'text/x-fortran',
+ '.fif' : 'application/fractals',
+ '.flr' : 'x-world/x-vrml',
+ '.flv' : 'video/x-flv',
+ '.for' : 'text/x-fortran',
+ '.gem' : 'application/octet-stream',
+ '.gemspec' : 'text/x-script.ruby',
+ '.gif' : 'image/gif',
+ '.gram' : 'application/srgs',
+ '.grxml' : 'application/srgs+xml',
+ '.gtar' : 'application/x-gtar',
+ '.gz' : 'application/x-gzip',
+ '.h' : 'text/x-c',
+ '.hdf' : 'application/x-hdf',
+ '.hh' : 'text/x-c',
+ '.hlp' : 'application/winhlp',
+ '.hqx' : 'application/mac-binhex40',
+ '.hta' : 'application/hta',
+ '.htc' : 'text/x-component',
+ '.htm' : 'text/html',
+ '.html' : 'text/html',
+ '.htt' : 'text/webviewhtml',
+ '.ice' : 'x-conference/x-cooltalk',
+ '.ico' : 'image/vnd.microsoft.icon',
+ '.ics' : 'text/calendar',
+ '.ief' : 'image/ief',
+ '.ifb' : 'text/calendar',
+ '.iges' : 'model/iges',
+ '.igs' : 'model/iges',
+ '.iii' : 'application/x-iphone',
+ '.ins' : 'application/x-internet-signup',
+ '.isp' : 'application/x-internet-signup',
+ '.iso' : 'application/octet-stream',
+ '.jar' : 'application/java-archive',
+ '.java' : 'text/x-java-source',
+ '.jfif' : 'image/pipeg',
+ '.jnlp' : 'application/x-java-jnlp-file',
+ '.jp2' : 'image/jp2',
+ '.jpe' : 'image/jpeg',
+ '.jpeg' : 'image/jpeg',
+ '.jpg' : 'image/jpeg',
+ '.js' : 'application/javascript',
+ '.json' : 'application/json',
+ '.kar' : 'audio/midi',
+ '.latex' : 'application/x-latex',
+ '.lha' : 'application/octet-stream',
+ '.lsf' : 'video/x-la-asf',
+ '.lsx' : 'video/x-la-asf',
+ '.lzh' : 'application/octet-stream',
+ '.log' : 'text/plain',
+ '.m13' : 'application/x-msmediaview',
+ '.m14' : 'application/x-msmediaview',
+ '.m3u' : 'audio/x-mpegurl',
+ '.m4a' : 'audio/mp4a-latm',
+ '.m4b' : 'audio/mp4a-latm',
+ '.m4p' : 'audio/mp4a-latm',
+ '.m4u' : 'video/vnd.mpegurl',
+ '.m4v' : 'video/mp4',
+ '.mac' : 'image/x-macpaint',
+ '.man' : 'text/troff',
+ '.mathml' : 'application/mathml+xml',
+ '.mbox' : 'application/mbox',
+ '.mdb' : 'application/x-msaccess',
+ '.mdoc' : 'text/troff',
+ '.me' : 'text/troff',
+ '.mesh' : 'model/mesh',
+ '.mht' : 'message/rfc822',
+ '.mhtml' : 'message/rfc822',
+ '.mid' : 'audio/midi',
+ '.midi' : 'audio/midi',
+ '.mif' : 'application/vnd.mif',
+ '.mime' : 'message/rfc822',
+ '.mml' : 'application/mathml+xml',
+ '.mng' : 'video/x-mng',
+ '.mny' : 'application/x-msmoney',
+ '.mov' : 'video/quicktime',
+ '.movie' : 'video/x-sgi-movie',
+ '.mp2' : 'video/mpeg',
+ '.mp3' : 'audio/mpeg',
+ '.mp4' : 'video/mp4',
+ '.mp4v' : 'video/mp4',
+ '.mpa' : 'video/mpeg',
+ '.mpe' : 'video/mpeg',
+ '.mpeg' : 'video/mpeg',
+ '.mpg' : 'video/mpeg',
+ '.mpga' : 'audio/mpeg',
+ '.mpp' : 'application/vnd.ms-project',
+ '.mpv2' : 'video/mpeg',
+ '.ms' : 'text/troff',
+ '.msh' : 'model/mesh',
+ '.msi' : 'application/x-msdownload',
+ '.mvb' : 'application/x-msmediaview',
+ '.mxu' : 'video/vnd.mpegurl',
+ '.nc' : 'application/x-netcdf',
+ '.nws' : 'message/rfc822',
+ '.oda' : 'application/oda',
+ '.odp' : 'application/vnd.oasis.opendocument.presentation',
+ '.ods' : 'application/vnd.oasis.opendocument.spreadsheet',
+ '.odt' : 'application/vnd.oasis.opendocument.text',
+ '.ogg' : 'application/ogg',
+ '.p' : 'text/x-pascal',
+ '.p10' : 'application/pkcs10',
+ '.p12' : 'application/x-pkcs12',
+ '.p7b' : 'application/x-pkcs7-certificates',
+ '.p7c' : 'application/x-pkcs7-mime',
+ '.p7m' : 'application/x-pkcs7-mime',
+ '.p7r' : 'application/x-pkcs7-certreqresp',
+ '.p7s' : 'application/x-pkcs7-signature',
+ '.pas' : 'text/x-pascal',
+ '.pbm' : 'image/x-portable-bitmap',
+ '.pct' : 'image/pict',
+ '.pdb' : 'chemical/x-pdb',
+ '.pdf' : 'application/pdf',
+ '.pem' : 'application/x-x509-ca-cert',
+ '.pfx' : 'application/x-pkcs12',
+ '.pgm' : 'image/x-portable-graymap',
+ '.pgn' : 'application/x-chess-pgn',
+ '.pgp' : 'application/pgp-encrypted',
+ '.pic' : 'image/pict',
+ '.pict' : 'image/pict',
+ '.pkg' : 'application/octet-stream',
+ '.pko' : 'application/ynd.ms-pkipko',
+ '.pl' : 'text/x-script.perl',
+ '.pm' : 'text/x-script.perl-module',
+ '.pma' : 'application/x-perfmon',
+ '.pmc' : 'application/x-perfmon',
+ '.pml' : 'application/x-perfmon',
+ '.pmr' : 'application/x-perfmon',
+ '.pmw' : 'application/x-perfmon',
+ '.png' : 'image/png',
+ '.pnm' : 'image/x-portable-anymap',
+ '.pnt' : 'image/x-macpaint',
+ '.pntg' : 'image/x-macpaint',
+ '.pot' : 'application/vnd.ms-powerpoint',
+ '.ppm' : 'image/x-portable-pixmap',
+ '.pps' : 'application/vnd.ms-powerpoint',
+ '.ppt' : 'application/vnd.ms-powerpoint',
+ '.prf' : 'application/pics-rules',
+ '.ps' : 'application/postscript',
+ '.psd' : 'image/vnd.adobe.photoshop',
+ '.pub' : 'application/x-mspublisher',
+ '.py' : 'text/x-script.python',
+ '.qt' : 'video/quicktime',
+ '.qti' : 'image/x-quicktime',
+ '.qtif' : 'image/x-quicktime',
+ '.ra' : 'audio/x-pn-realaudio',
+ '.rake' : 'text/x-script.ruby',
+ '.ram' : 'audio/x-pn-realaudio',
+ '.rar' : 'application/x-rar-compressed',
+ '.ras' : 'image/x-cmu-raster',
+ '.rb' : 'text/x-script.ruby',
+ '.rdf' : 'application/rdf+xml',
+ '.rgb' : 'image/x-rgb',
+ '.rm' : 'application/vnd.rn-realmedia',
+ '.rmi' : 'audio/mid',
+ '.roff' : 'text/troff',
+ '.rpm' : 'application/x-redhat-package-manager',
+ '.rss' : 'application/rss+xml',
+ '.rtf' : 'application/rtf',
+ '.rtx' : 'text/richtext',
+ '.ru' : 'text/x-script.ruby',
+ '.s' : 'text/x-asm',
+ '.scd' : 'application/x-msschedule',
+ '.sct' : 'text/scriptlet',
+ '.setpay' : 'application/set-payment-initiation',
+ '.setreg' : 'application/set-registration-initiation',
+ '.sgm' : 'text/sgml',
+ '.sgml' : 'text/sgml',
+ '.sh' : 'application/x-sh',
+ '.shar' : 'application/x-shar',
+ '.sig' : 'application/pgp-signature',
+ '.silo' : 'model/mesh',
+ '.sit' : 'application/x-stuffit',
+ '.skd' : 'application/x-koan',
+ '.skm' : 'application/x-koan',
+ '.skp' : 'application/x-koan',
+ '.skt' : 'application/x-koan',
+ '.smi' : 'application/smil',
+ '.smil' : 'application/smil',
+ '.snd' : 'audio/basic',
+ '.so' : 'application/octet-stream',
+ '.spc' : 'application/x-pkcs7-certificates',
+ '.spl' : 'application/x-futuresplash',
+ '.src' : 'application/x-wais-source',
+ '.sst' : 'application/vnd.ms-pkicertstore',
+ '.stl' : 'application/vnd.ms-pkistl',
+ '.stm' : 'text/html',
+ '.sv4cpio' : 'application/x-sv4cpio',
+ '.sv4crc' : 'application/x-sv4crc',
+ '.svg' : 'image/svg+xml',
+ '.svgz' : 'image/svg+xml',
+ '.swf' : 'application/x-shockwave-flash',
+ '.t' : 'text/troff',
+ '.tar' : 'application/x-tar',
+ '.tbz' : 'application/x-bzip-compressed-tar',
+ '.tcl' : 'application/x-tcl',
+ '.tex' : 'application/x-tex',
+ '.texi' : 'application/x-texinfo',
+ '.texinfo' : 'application/x-texinfo',
+ '.text' : 'text/plain',
+ '.tgz' : 'application/x-compressed',
+ '.tif' : 'image/tiff',
+ '.tiff' : 'image/tiff',
+ '.torrent' : 'application/x-bittorrent',
+ '.tr' : 'text/troff',
+ '.trm' : 'application/x-msterminal',
+ '.tsv' : 'text/tab-seperated-values',
+ '.txt' : 'text/plain',
+ '.uls' : 'text/iuls',
+ '.ustar' : 'application/x-ustar',
+ '.vcd' : 'application/x-cdlink',
+ '.vcf' : 'text/x-vcard',
+ '.vcs' : 'text/x-vcalendar',
+ '.vrml' : 'model/vrml',
+ '.vxml' : 'application/voicexml+xml',
+ '.war' : 'application/java-archive',
+ '.wav' : 'audio/x-wav',
+ '.wbmp' : 'image/vnd.wap.wbmp',
+ '.wbxml' : 'application/vnd.wap.wbxml',
+ '.wcm' : 'application/vnd.ms-works',
+ '.wdb' : 'application/vnd.ms-works',
+ '.wks' : 'application/vnd.ms-works',
+ '.wma' : 'audio/x-ms-wma',
+ '.wmf' : 'application/x-msmetafile',
+ '.wml' : 'text/vnd.wap.wml',
+ '.wmls' : 'text/vnd.wap.wmlscript',
+ '.wmlsc' : 'application/vnd.wap.wmlscriptc',
+ '.wmv' : 'video/x-ms-wmv',
+ '.wmx' : 'video/x-ms-wmx',
+ '.wps' : 'application/vnd.ms-works',
+ '.wri' : 'application/x-mswrite',
+ '.wrl' : 'model/vrml',
+ '.wrz' : 'x-world/x-vrml',
+ '.wsdl' : 'application/wsdl+xml',
+ '.xaf' : 'x-world/x-vrml',
+ '.xbm' : 'image/x-xbitmap',
+ '.xht' : 'application/xhtml+xml',
+ '.xhtml' : 'application/xhtml+xml',
+ '.xla' : 'application/vnd.ms-excel',
+ '.xlc' : 'application/vnd.ms-excel',
+ '.xlm' : 'application/vnd.ms-excel',
+ '.xls' : 'application/vnd.ms-excel',
+ '.xlt' : 'application/vnd.ms-excel',
+ '.xml' : 'application/xml',
+ '.xof' : 'x-world/x-vrml',
+ '.xpm' : 'image/x-xpixmap',
+ '.xsl' : 'application/xml',
+ '.xslt' : 'application/xslt+xml',
+ '.xul' : 'application/vnd.mozilla.xul+xml',
+ '.xwd' : 'image/x-xwindowdump',
+ '.xyz' : 'chemical/x-xyz',
+ '.yaml' : 'text/yaml',
+ '.yml' : 'text/yaml',
+ '.z' : 'application/x-compress',
+ '.zip' : 'application/zip'
+ };
+
+function mimeType(pathname, default_type) {
+ return extToType[path.extname(pathname)] || default_type || 'application/octet-stream';
+}
+
+exports.mimeType = mimeType;
341 lib/incoming_form.js
@@ -0,0 +1,341 @@
+var sys = require('sys')
+ , path = require('path')
+ , WriteStream = require('fs').WriteStream
+ , MultipartParser = require('./multipart_parser').MultipartParser
+ , QuerystringParser = require('./querystring_parser').QuerystringParser
+ , StringDecoder = require('string_decoder').StringDecoder
+ , EventEmitter = require('events').EventEmitter;
+
+function IncomingForm() {
+ EventEmitter.call(this);
+
+ this.error = null;
+ this.ended = false;
+
+ this.maxFieldsSize = 2 * 1024 * 1024;
+ this.keepExtensions = false;
+ this.uploadDir = '/tmp';
+ this.encoding = 'utf-8';
+ this.headers = null;
+ this.type = null;
+
+ this.bytesReceived = null;
+ this.bytesExpected = null;
+
+ this._parser = null;
+ this._flushing = 0;
+ this._fieldsSize = 0;
+};
+sys.inherits(IncomingForm, EventEmitter);
+exports.incomingForm = new IncomingForm();
+
+IncomingForm.prototype.parse = function(req, cb) {
+ this.pause = function() {
+ try {
+ req.pause();
+ } catch (err) {
+ // the stream was destroyed
+ if (!this.ended) {
+ // before it was completed, crash & burn
+ this._error(err);
+ }
+ return false;
+ }
+ return true;
+ };
+
+ this.resume = function() {
+ try {
+ req.resume();
+ } catch (err) {
+ // the stream was destroyed
+ if (!this.ended) {
+ // before it was completed, crash & burn
+ this._error(err);
+ }
+ return false;
+ }
+
+ return true;
+ };
+
+ this.writeHeaders(req.headers);
+
+ var self = this;
+ req
+ .addListener('error', function(err) {
+ self._error(err);
+ })
+ .addListener('data', function(buffer) {
+ self.write(buffer);
+ })
+ .addListener('end', function() {
+ if (self.error) {
+ return;
+ }
+
+ var err = self._parser.end();
+ if (err) {
+ self._error(err);
+ }
+ });
+
+ if (cb) {
+ var fields = {}, files = {};
+ this
+ .addListener('field', function(name, value) {
+ fields[name] = value;
+ })
+ .addListener('file', function(name, file) {
+ files[name] = file;
+ })
+ .addListener('error', function(err) {
+ cb(err, fields, files);
+ })
+ .addListener('end', function() {
+ cb(null, fields, files);
+ });
+ }
+
+ return this;
+};
+
+IncomingForm.prototype.writeHeaders = function(headers) {
+ this.headers = headers;
+ this._parseContentLength();
+ this._parseContentType();
+};
+
+IncomingForm.prototype.write = function(buffer) {
+ if (!this._parser) {
+ this._error(new Error('unintialized parser'));
+ return;
+ }
+
+ var bytesParsed = this._parser.write(buffer);
+ if (bytesParsed !== buffer.length) {
+ this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed'));
+ }
+
+ this.bytesReceived += bytesParsed;
+ this.emit('progress', this.bytesReceived, this.bytesExpected);
+
+ return bytesParsed;
+};
+
+IncomingForm.prototype.pause = function() {
+ // this does nothing, unless overwritten in IncomingForm.parse
+ return false;
+};
+
+IncomingForm.prototype.resume = function() {
+ // this does nothing, unless overwritten in IncomingForm.parse
+ return false;
+};
+
+IncomingForm.prototype.onPart = function(part) {
+ // this method can be overwritten by the user
+ this.handlePart(part);
+};
+
+IncomingForm.prototype.handlePart = function(part) {
+ var self = this;
+
+ if (!part.filename) {
+ var value = ''
+ , decoder = new StringDecoder(this.encoding);
+
+ part.addListener('data', function(buffer) {
+ self._fieldsSize += buffer.length;
+ if (self._fieldsSize > self.maxFieldsSize) {
+ self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data'));
+ return;
+ }
+ value += decoder.write(buffer);
+ });
+
+ part.addListener('end', function() {
+ self.emit('field', part.name, value);
+ });
+ return;
+ }
+
+ this._flushing++;
+
+ var file = new WriteStream(this._uploadPath(part.filename));
+ part.addListener('data', function(buffer) {
+ self.pause();
+ file.write(buffer, function() {
+ self.resume();
+ });
+ });
+
+ part.addListener('end', function() {
+ file.end(function() {
+ self._flushing--;
+ self.emit
+ ( 'file'
+ , part.name
+ , { path: file.path
+ , filename: part.filename
+ , mime: part.mime
+ }
+ );
+ self._maybeEnd();
+ });
+ });
+};
+
+IncomingForm.prototype._parseContentType = function() {
+ if (!this.headers['content-type']) {
+ this._error(new Error('bad content-type header, no content-type'));
+ return;
+ }
+
+ if (this.headers['content-type'].match(/urlencoded/i)) {
+ this._initUrlencoded();
+ return;
+ }
+
+ if (this.headers['content-type'].match(/multipart/i)) {
+ var m;
+ if (m = this.headers['content-type'].match(/boundary=([^;]+)/i)) {
+ this._initMultipart(m[1]);
+ } else {
+ this._error(new Error('bad content-type header, no multipart boundary'));
+ }
+ return;
+ }
+
+ this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type']));
+};
+
+IncomingForm.prototype._error = function(err) {
+ if (this.error) {
+ return;
+ }
+
+ this.error = err;
+ this.pause();
+ this.emit('error', err);
+};
+
+IncomingForm.prototype._parseContentLength = function() {
+ if (this.headers['content-length']) {
+ this.bytesReceived = 0;
+ this.bytesExpected = parseInt(this.headers['content-length'], 10);
+ }
+};
+
+IncomingForm.prototype._newParser = function() {
+ return new MultipartParser();
+};
+
+IncomingForm.prototype._initMultipart = function(boundary) {
+ this.type = 'multipart';
+
+ var parser = new MultipartParser()
+ , self = this
+ , headerField
+ , headerValue
+ , part;
+
+ parser.initWithBoundary(boundary);
+
+ parser.onPartBegin = function() {
+ part = new EventEmitter();
+ part.headers = {};
+ part.name = null;
+ part.filename = null;
+ part.mime = null;
+ headerField = '';
+ headerValue = '';
+ };
+
+ parser.onHeaderField = function(b, start, end) {
+ headerField += b.toString(self.encoding, start, end);
+ };
+
+ parser.onHeaderValue = function(b, start, end) {
+ headerValue += b.toString(self.encoding, start, end);
+ };
+
+ parser.onHeaderEnd = function() {
+ headerField = headerField.toLowerCase();
+ part.headers[headerField] = headerValue;
+
+ var m;
+ if (headerField == 'content-disposition') {
+ if (m = headerValue.match(/name="([^"]+)"/i)) {
+ part.name = m[1];
+ }
+
+ if (m = headerValue.match(/filename="([^"]+)"/i)) {
+ part.filename = m[1].substr(m[1].lastIndexOf('\\') + 1);
+ }
+ } else if (headerField == 'content-type') {
+ part.mime = headerValue;
+ }
+
+ headerField = '';
+ headerValue = '';
+ };
+
+ parser.onHeadersEnd = function() {
+ self.onPart(part);
+ };
+
+ parser.onPartData = function(b, start, end) {
+ part.emit('data', b.slice(start, end));
+ };
+
+ parser.onPartEnd = function() {
+ part.emit('end');
+ };
+
+ parser.onEnd = function() {
+ self.ended = true;
+ self._maybeEnd();
+ };
+
+ this._parser = parser;
+};
+
+IncomingForm.prototype._initUrlencoded = function() {
+ this.type = 'urlencoded';
+
+ var parser = new QuerystringParser()
+ , self = this;
+
+ parser.onField = function(key, val) {
+ self.emit('field', key, val);
+ };
+
+ parser.onEnd = function() {
+ self.ended = true;
+ self._maybeEnd();
+ };
+
+ this._parser = parser;
+};
+
+IncomingForm.prototype._uploadPath = function(filename) {
+ var name = '';
+ for (i = 0; i < 32; i++) {
+ name += Math.floor(Math.random() * 16).toString(16);
+ }
+
+ if (this.keepExtensions) {
+ name += path.extname(filename);
+ }
+
+ return path.join(this.uploadDir, name);
+};
+
+IncomingForm.prototype._maybeEnd = function() {
+ if (!this.ended || this._flushing) {
+ return;
+ }
+
+ this.emit('end');
+};
401 lib/jssp.js
@@ -0,0 +1,401 @@
+var EventEmitter = require('events').EventEmitter,
+ http = require('http'),
+ fs = require('fs'),
+ mime = require('./content-type'),
+ pathlib = require('path'),
+ uri = require('url'),
+ log = require('./log'),
+ sys = require('sys'),
+ Script = process.binding('evals').Script,
+ utils = require('./utils'),
+ sessionManager = require('./session'),
+ incomingForm = require('./incoming_form').incomingForm,
+ multi = require('./multinode/multi-node'),
+ asemaphore = require('./asemaphore'),
+ applicationManager = require('./application');
+
+var cachedJssp = [];
+var lib = new Object();
+var globalSettings;
+var applicationScope;
+var sessionScope;
+
+exports.start = function(settings) {
+ globalSettings = defaultSettings(settings);
+ if(globalSettings.debug_mode)
+ log.level = log.levels.DEBUG;
+ else
+ log.level = log.levels.WARN;
+ log.info("Starting Web App - " +globalSettings.web_app_name);
+ //setting up sessionScope and applicationScope:
+ applicationScope = applicationManager.start(globalSettings.web_app_name,globalSettings.server_script.memcached);
+ sessionScope = sessionManager.start(globalSettings.server_script.session_minutes,globalSettings.web_app_name);
+
+ //Load utils dynamicly
+ if(globalSettings.path.lib != undefined)
+ fs.stat(globalSettings.path.lib, function (err, stats) {
+ if (err)
+ log.error("Loading lib error:"+err);
+ else if(!stats.isDirectory())
+ log.error("Loading lib error: path must be a directory");
+ else{
+ var files = fs.readdirSync(globalSettings.path.lib);
+ for(i=0;i<files.length;i++){
+ var file = files[i];
+ if(files[i].endsWith(".js")){
+ var fileNoJs = file.substring(0,files[i].length-3);
+ var path = pathlib.join(globalSettings.path.lib,fileNoJs);
+ eval("lib."+fileNoJs+" = require(path)");
+ log.info("Loading lib file:" + path);
+ }
+ }
+ }
+ });
+ //END util loading
+ var server = http.createServer(function (req, res) {
+ log.debug("request arrived!");
+ var url = uri.parse(req.url,true);
+ var pathname = (url.pathname || '/');
+ var cleanPathname = pathname
+ .replace(/\.\.\//g,'') //disallow parent directory access
+ .replace(/\%20/g,' '); //convert spaces
+ if(req.method == "GET"){
+ var params = url.query;
+ if(params == undefined)
+ params = new Object();
+ req.parameters = params;
+ handleRequest(req,res,cleanPathname);
+ }
+ else if (req.method == "POST"){
+ incomingForm.parse(req, function(err, fields, files) {
+ //log.debug("POST fields:" + utils.arrayToString(fields));
+ params = new Object();
+ req.parameters = fields;
+ handleRequest(req,res,cleanPathname);
+ });
+ }
+ else //Other Methods
+ handleRequest(req,res,cleanPathname);
+
+
+ });
+ multi.listen({
+ port: globalSettings.port,
+ nodes: globalSettings.nodes
+ }, server);
+
+ console.log('Server running at port '+globalSettings.port);
+};
+
+function handleRequest(req,res,cleanPathname){
+ var root = globalSettings.path.root;
+ var path = pathlib.join(root, cleanPathname);
+ log.info("Handling request to: " +path + " pid("+process.pid+")");
+ //log.debug("Request headers: "+utils.arrayToString(req.headers));
+ fs.stat(path, function (err, stats) {
+ if (err) {
+ // ENOENT is normal on 'file not found'
+ if (err.errno != process.ENOENT) {
+ // any other error is abnormal - log it
+ log.error("fs.stat(",path,") failed: ", err);
+ }
+ return fileNotFound(req,res,path);
+ }
+ if (!stats.isFile())
+ return fileNotFound(req,res,path);
+ else{
+ if (stats.isDirectory())
+ path.join(path, "index.html");
+ var cookie = req.headers["cookie"];
+ var sessionId = utils.getSessionId(cookie);
+ sessionScope.hello(sessionId);
+ if(!path.endsWith("."+globalSettings.server_script.ext)){
+ sendHeaders(req, res,undefined, stats.size, mime.mimeType(path), stats.mtime);
+ var readStream = fs.createReadStream(path);
+ sys.pump(readStream,res);
+ }else{
+
+ if(cachedJssp[path] == undefined || cachedJssp[path+"#date"] != stats.mtime.toUTCString()){
+ if(cachedJssp[path] != undefined)
+ log.debug("CacheDate = "+cachedJssp[path+"#date"] +" REAL DATE = "+stats.mtime.toUTCString());
+ var readStream = fs.createReadStream(path);
+ var script = [];
+ readStream.addListener("data", function (chunk) {
+ script.push(chunk.toString());
+ });
+ readStream.addListener("end", function () {
+ log.info("STARTING PROCESSING JSSP");
+ var forward = serverSideProcessing(script.join(""),req,res,path,stats.mtime,sessionId);
+ log.info("END OF JSSP PROCESSING");
+ });
+ req.connection.addListener('timeout', function() {
+ /* dont destroy it when the fd's already closed */
+ if (readStream.readable) {
+ log.debug('timed out. destroying file read stream');
+ readStream.destroy();
+ }
+ });
+
+ readStream.addListener('fd', function(fd) {
+ log.debug("opened",path,"on fd",fd);
+ });
+
+ readStream.addListener('error', function (err) {
+ log.error('error reading',file,sys.inspect(err));
+ resp.end('');
+ });
+ res.addListener('error', function (err) {
+ log.error('error writing',file,sys.inspect(err));
+ readStream.destroy();
+ });
+ }
+ else{
+ log.info("RUN JSSP FROM CACHE");
+ serverSideRunning(cachedJssp[path],req,res,path,stats.mtime,sessionId);
+ }
+
+ }
+
+ }
+
+ });
+};
+
+function serverSideRunning(newfileName,request,response,file,lastMod,sessionId){
+ var responseHead= new Object();
+ var result = new Object();
+ result.html = "";
+ var allGood = true;
+ responseHead.status = 200;
+ responseHead.headers = {};
+ var afterEval = [];
+ var currentAsemaphore = asemaphore.ctor(1,function(){
+ result.html = afterEval.join("");
+ if(allGood){//otherwise, forwarding...
+ sendHeaders(request,response,responseHead,result.html.length,mime.mimeType(file,"text/html"),lastMod,result.sessionId);
+ response.end(result.html);
+ }
+ });
+ var commands = {
+ write : function(text){
+ //log.debug("WRITE afterEval : "+ afterEval);
+ afterEval.push(text);
+ },
+ writeEscapedText :function(text){
+ afterEval.push(unescape(text));
+ },
+ forward :function(resource){
+ handleRequest(request,response,resource);
+ allGood = false;
+ },
+ sendRedirect:function(url){
+ responseHead.status = 301;
+ responseHead.headers["location"] = url;
+ }
+// runToNextWaitPoint : function(){
+// currentAsemaphore.v();
+// },
+// waitPoint : function(){
+// currentAsemaphore.p();
+// },
+// addFunc : function(func){
+// currentAsemaphore.addFunction(func);
+// }
+ };
+ //Waiting for a bug fix(V8)
+ /*try{
+
+ var script = require(newfileName);
+ script.run(lib,application,request,responseHead,writeEscapedText,forward,sendRedirect,write,session);
+ result.html = afterEval.join("");
+ log.debug("ServerSide result:"+utils.arrayToString(result));
+ }
+ catch(er){
+ log.warn("parse problem:"+err);
+ responseHead.status = 500;
+ result.html = "<h1>"+globalSettings.web_app_name+" - SERVER ERROR</h1>";
+ if(globalSettings.debug_mode){
+ result.html += "(Debug Mode) Could not parse jssp<br/> ";
+ var erStr = "Details: ";
+ result.html += erStr+er.stack;
+ }
+ else
+ result.html += "Could not parse jssp";
+ }
+ if(allGood){//otherwise, we are probably forwarding...
+ var ctHTML = "text/html";
+ sendHeaders(request,response,responseHead,result.html.length,mime.mimeType(file,ctHTML),lastMod,result.sessionId);
+ response.end(result.html);
+ }*/
+
+ fs.readFile([newfileName,".js"].join(""), function (errRead, data) {
+ try{
+ if (errRead) throw errRead;
+ //Handling session and application with asemphore
+ result.sessionId = sessionId;
+ var application = applicationManager.getManager(currentAsemaphore);
+ log.debug(application);
+ var session = sessionManager.getManager(sessionId,currentAsemaphore,application);
+ //-----------
+ command = ["functoRun = ",data.toString()].join("");
+ eval(command);
+ functoRun(log,lib,application,request,responseHead,commands,session);
+ currentAsemaphore.p();
+ }catch(err){
+ log.warn("parse problem:"+err);
+ allGood = true;//yes all is good!!! :(
+ responseHead.status = 500;
+ result.html = ["<h1>",globalSettings.web_app_name," - SERVER ERROR</h1>."].join("");
+ if(globalSettings.debug_mode){
+ result.html += "(Debug Mode) Could not parse jssp<br/> ";
+ var erStr = "Details: ";
+ result.html += erStr+err.stack;
+ }
+ else
+ result.html += "Could not parse jssp";
+ currentAsemaphore.p();
+ }
+ });
+}
+
+function serverSideProcessing(str,request,response,file,lastMod,sessionId){
+ var toEvalArray = [];
+ var startTag = globalSettings.server_script.begin;
+ var startWriteAddition = globalSettings.server_script.begin_additional_write;
+ var endTag = globalSettings.server_script.end;
+ var lineArray = str.split(new RegExp( "\\n", "g" ));
+ var isInScript = false;
+ var currentScript =[];
+ var nextLine = "\n";
+ for(index=0;index<lineArray.length;index++){
+ line = lineArray[index];
+ while(line.length>0){
+ if(!isInScript){
+ var startTagIndex = line.indexOf(startTag);
+ if(line.indexOf(startTag)==-1){
+ toEvalArray.push('commands.writeEscapedText("'+escape(line+nextLine)+'");'+nextLine);
+ line="";
+ }
+ else{
+ lineBeforeStart = line.substring(0,startTagIndex);
+ toEvalArray.push('commands.writeEscapedText("'+escape(lineBeforeStart)+'");');
+ line = line.substring(startTagIndex+startTag.length);
+ if(line.length==0)
+ toEvalArray.push(nextLine);
+ isInScript = true;
+ }
+ }
+ else{//Inscript
+ var endTagIndex =line.indexOf(endTag);
+ if(line.indexOf(endTag)==-1){
+ currentScript.push(line+nextLine);
+ line="";
+ }
+ else{
+ lineBeforeEnd = line.substring(0,endTagIndex);
+ currentScript.push(lineBeforeEnd);
+ var theScript = currentScript.join("");
+ if(theScript.startsWith(startWriteAddition)) //handling <?=...?> cases
+ theScript = "commands.write("+theScript.substring(startWriteAddition.length)+");";
+ toEvalArray.push(theScript);
+ currentScript = [];
+ line = line.substring(endTagIndex+endTag.length);
+ if(line.length==0)
+ toEvalArray.push(nextLine);
+
+ isInScript = false;
+ }
+ }
+ }
+ }
+
+ var toEval =toEvalArray.join("");
+ //Waiting for a bug fix(V8)
+ //http://groups.google.com/group/nodejs/browse_thread/thread/7a2e409ec970198e/d9336b7b2764f129?lnk=gst&q=require+exception#d9336b7b2764f129
+ //var finalFunction = "exports.run = (function(lib,application,request,responseHead,writeEscapedText,forward,sendRedirect,write,session) {"
+ // +toEval+"})";
+ var finalFunction = "(function(log,lib,application,request,responseHead,commands,session) {"+toEval+"})";
+ var newfileName = file.substring(0,file.length-globalSettings.server_script.ext.length-1);
+ fs.writeFile(newfileName+".js", finalFunction, function (err) {
+ //log.debug("Error writin cache file: "+err);
+ cachedJssp[file] = newfileName;
+ cachedJssp[file + "#date"] = lastMod.toUTCString();
+ log.info("Caching - "+file + ",last mod - "+ lastMod.toUTCString());
+ serverSideRunning(newfileName,request,response,file,lastMod,sessionId);
+ });
+
+}
+
+function sendHeaders(req, res,responseHead, length, content_type, modified_time,sessionId) {
+ if(responseHead==undefined)
+ responseHead = new Object();
+ if(responseHead.status==undefined)
+ responseHead.status = 200;
+ if(responseHead.headers==undefined)
+ responseHead.headers = {};
+ responseHead.headers["date"] = (new Date()).toUTCString();
+ responseHead.headers["Server"] = "Alligator/0.3+ Node.js/"+process.version;
+ if(sessionId != undefined)
+ if(responseHead.headers["Set-cookie"] == undefined)//TODO add expiary and domain
+ responseHead.headers["Set-cookie"] = "njssession="+sessionId;
+ else
+ responseHead.headers["Set-cookie"] += ";njssession="+sessionId;
+ if (length)
+ responseHead.headers["Content-Length"] = length;
+ if (content_type)
+ responseHead.headers["Content-Type"] = content_type || "application/octet-stream";
+ if (modified_time)
+ responseHead.headers["Last-Modified"] = modified_time.toUTCString();
+ //log.debug("RESPONSE Headers :"+utils.arrayToString(responseHead.headers)+" +++ RESPONSE Status :"+responseHead.status);
+ res.writeHead(responseHead.status, responseHead.headers);
+ log.info(req.connection.remoteAddress,req.method,responseHead.status,length);
+}
+
+function fileNotFound(req,res,path) {
+ log.debug("404 opening path: '"+path+"'");
+ var body = "404: " + req.url + " not found.\n";
+ var responseHead= new Object();
+ responseHead.status = 404;
+ sendHeaders(req, res,responseHead,body.length,"text/plain");
+ if (req.method != 'HEAD')
+ res.end(body, 'utf-8');
+ else
+ res.end('');
+}
+
+function defaultSettings(settings){
+ if(settings.web_app_name == undefined)
+ settings.web_app_name = "AlligatorWebApp ";
+ if(settings.port == undefined)
+ settings.port = 80;
+ if(settings.path == undefined)
+ settings.path = new Object();
+ if(settings.path.root == undefined)
+ settings.path.root = "WWW";
+ if(settings.server_script == undefined)
+ settings.server_script = new Object();
+ if(settings.server_script.ext == undefined)
+ settings.server_script.ext = "jssp";
+ if(settings.server_script.begin == undefined)
+ settings.server_script.begin = "<?";
+ if(settings.server_script.end == undefined)
+ settings.server_script.end = "?>";
+ if(settings.server_script.begin_additional_write == undefined)
+ settings.server_script.begin_additional_write = "=";
+ //memcached
+ if(settings.server_script.memcached == undefined)
+ settings.server_script.memcached = new Object();
+ if(settings.server_script.memcached.enable == undefined)
+ settings.server_script.memcached.enable = 0;
+ if(settings.server_script.memcached.server == undefined)
+ settings.server_script.memcached.server = "localhost";
+ if(settings.server_script.memcached.port == undefined)
+ settings.server_script.memcached.port = 11211;
+
+ if(settings.debug_mode == undefined)
+ settings.debug_mode = 1;
+ if(settings.nodes == undefined)
+ settings.nodes = 1;
+ return settings;
+}
+
33 lib/log.js
@@ -0,0 +1,33 @@
+var sys = require('sys');
+
+function log(level, args) {
+ if (level >= exports.level) {
+ sys.print((new Date()).toUTCString() +"("+level+"): ");
+
+ // Convert arguments object to an array so we can use
+ // Array's join method on it
+ sys.puts(Array.prototype.slice.call(args).join(' '));
+ }
+}
+
+var levels = exports.levels = {
+ "DEBUG" : 0,
+ "INFO" : 1,
+ "WARN" : 2,
+ "ERROR" : 3
+};
+
+exports.level = levels.WARN;
+
+exports.debug = function() {
+ log(levels.DEBUG, arguments);
+};
+exports.warn = function() {
+ log(levels.WARN, arguments);
+};
+exports.info = function() {
+ log(levels.INFO, arguments);
+};
+exports.error = function() {
+ log(levels.ERROR, arguments);
+};
81 lib/mc/connection.js
@@ -0,0 +1,81 @@
+var Memcache = {
+ Request:require('./request')
+};
+var net = require('net');
+var sys = require('sys');
+
+Memcache.Connection = function(host, port){
+ this.host = host;
+ this.port = port;
+};
+
+sys.inherits(Memcache.Connection, process.EventEmitter);
+
+Memcache.Connection.prototype.processRequest = function(request) {
+ // todo: implement some kind of queueing mechanism here
+ if (this.request) return;
+ this.emit('status', 'busy');
+ this.request = request;
+ if (!(this.request instanceof Memcache.Request)) this.request = new Memcache.Request(this.request);
+ this.request.setConnection(this);
+ this.getTcpConnection(function(connection){
+ connection.write(request.command + '\r\n');
+ if (request.data) connection.write(request.data + '\r\n');
+ });
+};
+
+Memcache.Connection.prototype.isBusy = function() {
+ return this.request != undefined;
+};
+
+Memcache.Connection.prototype.getTcpConnection = function(callback) {
+ if (this.tcpConnection == undefined) {
+ // no connection established? let's start a new one
+ var connection = net.createConnection(this.port, this.host);
+ if (callback) connection.addListener('connect', function(){
+ callback(connection);
+ });
+ // ugly closure
+ var method = this;
+ // add event listeners
+ connection.addListener('data', function(){
+ method.request.parseResponse.apply(method.request, arguments);
+ });
+ connection.addListener('close', function(){
+ method.close.apply(method, arguments);
+ });
+ this.tcpConnection = connection;
+ } else {
+ // connection already established? use this one
+ if (callback) callback(this.tcpConnection);
+ }
+ return this.tcpConnection;
+};
+
+Memcache.Connection.prototype.close = function() {
+ if (!this.tcpConnection) return;
+ this.tcpConnection.end();
+ delete this.tcpConnection;
+ if (this.request) {
+ this.request.finish('ERROR');
+ }
+ this.emit('close');
+};
+
+Memcache.Connection.prototype.finishRequest = function(request) {
+ if (request != this.request) return;
+ delete this.request;
+ this.emit('status', 'idle');
+};
+/*
+Memcache.Connection.prototype.setPool = function(pool) {
+ this.pool = pool;
+};
+
+Memcache.Connection.prototype.getPool = function() {
+ if (this.pool) return this.pool;
+ return false;
+};
+*/
+
+module.exports = Memcache.Connection;
114 lib/mc/memcache.js
@@ -0,0 +1,114 @@
+// this is for easier combining of objects
+// but it might interfere with other (foreign) code
+// TODO replace with something less obstrusive
+Object.prototype.apply = function(values) {
+ for (var key in values) {
+ this[key] = values[key];
+ }
+ return this;
+};
+
+Memcache = function(host, port){
+ this.host = host ? host : 'localhost';
+ this.port = port ? port : 11211;
+};
+
+Memcache.Connection = require('./connection');
+Memcache.Pool = require('./pool');
+
+Memcache.pooling = true;
+
+Memcache.prototype.getConnection = function(){
+ if (!this.connection) {
+ this.connection = new Memcache.Connection(this.host, this.port);
+ }
+ return this.connection;
+ return this.getPool().getConnection();
+};
+
+Memcache.prototype.processRequest = function(request){
+ if (Memcache.pooling) {
+ return this.getPool().processRequest(request);
+ } else {
+ return this.getConnection().processRequest(request);
+ }
+};
+
+Memcache.prototype.getPool = function(){
+ if (!this.pool) {
+ this.pool = new Memcache.Pool({
+ host:this.host,
+ port:this.port
+ });
+ }
+ return this.pool;
+};
+
+Memcache.prototype.get = function(key, callback){
+ var request = {
+ command:'get ' + key
+ };
+ if (callback) request.callback = callback;
+ this.processRequest(request);
+};
+
+Memcache.prototype.set = function(key, value, options){
+ options = {
+ expires:0,
+ flags:0
+ }.apply(options);
+ var request = {
+ command:'set ' + key + ' ' + options.flags + ' ' + options.expires + ' ' + value.length,
+ data:value
+ };
+ if (options.callback) request.callback = options.callback;
+ this.processRequest(request);
+};
+
+Memcache.prototype.add = function(key, value, options){
+ options = {
+ expires:0,
+ flags:0
+ }.apply(options);
+ var request = {
+ command:'add ' + key + ' ' + options.flags + ' ' + options.expires + ' ' + value.length,
+ data:value
+ };
+ if (options.callback) request.callback = options.callback;
+ this.processRequest(request);
+};
+
+Memcache.prototype.append = function(key, value, options){
+ options = {}.apply(options);
+ var request = {
+ command:'append ' + key + ' 0 0 ' + value.length,
+ data:value
+ };
+ if (options.callback) request.callback = options.callback;
+ this.processRequest(request);
+};
+
+Memcache.prototype.prepend = function(key, value, options){
+ options = {}.apply(options);
+ var request = {
+ command:'prepend ' + key + ' 0 0 ' + value.length,
+ data:value
+ };
+ if (options.callback) request.callback = options.callback;
+ this.processRequest(request);
+};
+
+Memcache.prototype.del = function(key, options){
+ options = {}.apply(options);
+ var request = {
+ command:'delete ' + key
+ };
+ if (options.callback) request.callback = options.callback;
+ this.processRequest(request);
+};
+
+Memcache.prototype.shutdown = function(){
+ if (this.connection) this.connection.close();
+};
+
+module.exports = Memcache;
72 lib/mc/pool.js
@@ -0,0 +1,72 @@
+var Memcache = {
+ Connection:require('./connection')
+};
+
+Memcache.Pool = function(options){
+ // default settings
+ this.apply({
+ maxConnections:10,
+ host:'localhost',
+ port:11211,
+ pool:[]
+ });
+ // user-specified settings
+ if (options) this.apply(options);
+ // start a queue
+ this.queue = [];
+};
+
+Memcache.Pool.prototype.getConnection = function(){
+ for (var i = 0; i < this.pool.length; i++) {
+ if (!this.pool[i].isBusy()) return this.pool[i];
+ }
+ if (this.pool.length < this.maxConnections) {
+ var connection = new Memcache.Connection(this.host, this.port);
+ this.addConnection(connection);
+ return connection;
+ }
+ require('sys').puts('unable to open additional connections - max # of connections reached');
+ return false;
+};
+
+Memcache.Pool.prototype.addConnection = function(connection){
+ var method = this;
+ connection.addListener('status', function(status) {
+ if (status == 'idle') method.processQueue.apply(method, [connection]);
+ //require('sys').puts('status is now ' + status);
+ });
+ connection.addListener('close', function() {
+ method.removeConnection(connection);
+ });
+ this.pool.push(connection);
+ //require('sys').puts('# of connections is now: ' + this.pool.length);
+};
+
+Memcache.Pool.prototype.removeConnection = function(connection){
+ for (var i = 0; i < this.pool.length; i++){
+ if (this.pool[i] == connection) {
+ this.pool.splice(i, 1);
+ //TODO this should be more specific, i.e. include a second parameter
+ //connection.removeListener('status');
+ //connection.removeListener('close');
+ }
+ }
+ //require('sys').puts('# of connections is now: ' + this.pool.length);
+};
+
+Memcache.Pool.prototype.processQueue = function(connection) {
+ if (this.queue.length == 0) return;
+ if (connection && !connection.isBusy()) {
+ connection.processRequest(this.queue.pop());
+ return;
+ }
+ connection = this.getConnection();
+ if (connection) connection.processRequest(this.queue.pop());
+};
+
+Memcache.Pool.prototype.processRequest = function(request) {
+ this.queue.push(request);
+ this.processQueue();
+};
+
+module.exports = Memcache.Pool;
52 lib/mc/request.js
@@ -0,0 +1,52 @@
+var Memcache = {};
+
+Memcache.Request = function(config){
+ if (config) this.apply(config);
+};
+
+Memcache.Request.prototype.setConnection = function(connection){
+ this.connection = connection;
+};
+
+Memcache.Request.prototype.parseResponse = function(data){
+ if (typeof(data) != 'string') data = data.toString();
+ while (data.length > 0) {
+ if (!this.dataMode) {
+ var index = data.indexOf('\r\n');
+ var response = data.substr(0, index);
+ var data = data.substr(index + 2, data.length - 2);
+ if (response.substring(0, 5) == 'VALUE') {
+ this.dataMode = true;
+ var split = response.split(' ');
+ this.expectedLength = split[3];
+ } else if (response == 'END' || response == 'ERROR' || response == 'STORED' || response == 'DELETED' || response == 'NOT_FOUND' || response == 'NOT_STORED') {
+ this.finish(response);
+ } else {
+ // unknown response string
+ require('sys').puts(response);
+ }
+ } else {
+ var chunk = data.substr(0, this.expectedLength);
+ this.expectedLength -= chunk.length;
+ if (this.data) {
+ this.data += chunk;
+ } else {