Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

initial version

  • Loading branch information...
commit 55a8e3df5f6b3ecdfb3cc5229f53af574cc56323 0 parents
@dan-manges authored
2  .gitignore
@@ -0,0 +1,2 @@
+/node_modules
+
19 LICENSE
@@ -0,0 +1,19 @@
+Copyright (C) 2011 by Dan Manges
+
+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.
55 README.md
@@ -0,0 +1,55 @@
+m2node
+======
+
+m2node is a mongrel2 handler for node
+
+install
+-------
+
+ npm install m2node
+
+example
+-------
+
+```javascript
+var http = require('http'),
+ m2node = require('m2node');
+
+var server = http.createServer(function (req, res) {
+ res.writeHead(200, {'Content-Type': 'text/plain'});
+ res.end('Hello World\n');
+});
+
+m2node.run(server, {
+ send_spec: 'tcp://127.0.0.1:9996',
+ recv_spec: 'tcp://127.0.0.1:9997'
+});
+```
+
+configuration
+-------------
+
+The configuration is from the perspective of the handler, so the send_spec in your mongrel2 config should match the recv_spec in your node config.
+
+example - express
+-----------------
+
+here's an example of serving an app built using the [express framework](http://expressjs.com/) with m2node
+
+```javascript
+var express = require('express'),
+ m2node = require('m2node');
+
+var app = express.createServer();
+
+app.get('/', function (req, res) {
+ res.send('Hello World')
+});
+
+m2node.run(app, {
+ send_spec: 'tcp://127.0.0.1:9996'
+ recv_spec: 'tcp://127.0.0.1:9997'
+});
+```
+
+
35 Rakefile
@@ -0,0 +1,35 @@
+SPEC_DIR = File.dirname(__FILE__) + "/spec"
+
+task :default => %w[compile test]
+
+desc "compile coffee to js"
+task :compile do
+ sh "coffee -c examples lib"
+end
+
+desc "run the tests"
+task :test do
+ sh "vows spec/m2node_spec.js"
+end
+
+namespace :test do
+ namespace :app do
+ task :start => :compile do
+ sh "coffee spec/app.coffee"
+ end
+ end
+
+ namespace :m2 do
+ task :load do
+ Dir.chdir(SPEC_DIR) do
+ sh "m2sh load --config mongrel2.conf --db config.sqlite"
+ end
+ end
+
+ task :start => :load do
+ Dir.chdir(SPEC_DIR) do
+ sh "m2sh start -name m2node_tests --db config.sqlite"
+ end
+ end
+ end
+end
1  examples/.gitignore
@@ -0,0 +1 @@
+config.sqlite
14 examples/express-server.coffee
@@ -0,0 +1,14 @@
+express = require 'express'
+m2node = require 'm2node'
+
+app = express.createServer()
+
+app.get('/', (req, res) ->
+ res.send('Hello World')
+)
+
+m2node.run(
+ app,
+ send_spec: 'tcp://127.0.0.1:9996'
+ recv_spec: 'tcp://127.0.0.1:9997'
+)
13 examples/express-server.js
@@ -0,0 +1,13 @@
+(function() {
+ var app, express, m2node;
+ express = require('express');
+ m2node = require('m2node');
+ app = express.createServer();
+ app.get('/', function(req, res) {
+ return res.send('Hello World');
+ });
+ m2node.run(app, {
+ send_spec: 'tcp://127.0.0.1:9996',
+ recv_spec: 'tcp://127.0.0.1:9997'
+ });
+}).call(this);
2  examples/logs/.gitignore
@@ -0,0 +1,2 @@
+*.log
+
24 examples/mongrel2.conf
@@ -0,0 +1,24 @@
+m2node_example = Handler(
+ send_spec = 'tcp://127.0.0.1:9997',
+ send_ident = '81b7114c-534c-4107-9f17-b317cfd59f62',
+ recv_spec = 'tcp://127.0.0.1:9996',
+ recv_ident = ''
+)
+
+localhost = Host(name = 'localhost', routes = {
+ '/': m2node_example
+})
+
+main = Server(
+ name = "m2node_examples",
+ port = 9000,
+ uuid = '5dc1fbe7-d9db-4602-8d19-80c7ef2b1b11',
+ access_log = "/logs/access.log",
+ error_log = "/logs/error.log",
+ chroot = ".",
+ default_host = "localhost",
+ pid_file = "/run/mongrel2.pid",
+ hosts = [localhost]
+)
+
+servers = [main]
15 examples/node-server.coffee
@@ -0,0 +1,15 @@
+http = require 'http'
+m2node = require 'm2node'
+
+server = http.createServer((req, res) ->
+ console.log("#{req.method} #{req.url}")
+ res.writeHead(200, {'Content-Type': 'text/plain'})
+ res.end('Hello World\n')
+)
+
+m2node.run(
+ server,
+ send_spec: 'tcp://127.0.0.1:9996'
+ recv_spec: 'tcp://127.0.0.1:9997'
+)
+
16 examples/node-server.js
@@ -0,0 +1,16 @@
+(function() {
+ var http, m2node, server;
+ http = require('http');
+ m2node = require('m2node');
+ server = http.createServer(function(req, res) {
+ console.log("" + req.method + " " + req.url);
+ res.writeHead(200, {
+ 'Content-Type': 'text/plain'
+ });
+ return res.end('Hello World\n');
+ });
+ m2node.run(server, {
+ send_spec: 'tcp://127.0.0.1:9996',
+ recv_spec: 'tcp://127.0.0.1:9997'
+ });
+}).call(this);
1  examples/run/.gitignore
@@ -0,0 +1 @@
+*.pid
1  examples/tmp/.git_empty_dir
@@ -0,0 +1 @@
+
14 lib/m2node.coffee
@@ -0,0 +1,14 @@
+{FakeSocket} = require './m2node/fake_socket'
+{Handler} = require './m2node/handler'
+
+exports.version = '0.1.0'
+
+exports.run = (server, options) ->
+ handler = new Handler(options)
+ handler.on 'request', (request) ->
+ fakeSocket = new FakeSocket()
+ fakeSocket.on 'write', ->
+ handler.sendResponse(request, fakeSocket.writeBuffer)
+ server.emit 'connection', fakeSocket
+ fakeSocket.emitData(request.toFullHttpRequest())
+
19 lib/m2node.js
@@ -0,0 +1,19 @@
+(function() {
+ var FakeSocket, Handler;
+ FakeSocket = require('./m2node/fake_socket').FakeSocket;
+ Handler = require('./m2node/handler').Handler;
+ exports.version = '0.1.0';
+ exports.run = function(server, options) {
+ var handler;
+ handler = new Handler(options);
+ return handler.on('request', function(request) {
+ var fakeSocket;
+ fakeSocket = new FakeSocket();
+ fakeSocket.on('write', function() {
+ return handler.sendResponse(request, fakeSocket.writeBuffer);
+ });
+ server.emit('connection', fakeSocket);
+ return fakeSocket.emitData(request.toFullHttpRequest());
+ });
+ };
+}).call(this);
28 lib/m2node/fake_socket.coffee
@@ -0,0 +1,28 @@
+sys = require 'sys'
+util = require 'util'
+events = require('events')
+
+class FakeSocket extends events.EventEmitter
+ constructor: ->
+ @writeBuffer = new Buffer('')
+ @writable = true
+
+ destroy: -> # noop
+ destroySoon: -> # noop
+
+ emitData: (buffer) ->
+ if (@_events && this._events['data'])
+ @emit('data', buffer)
+ if (@ondata)
+ @ondata(buffer, 0, buffer.length)
+
+ setTimeout: (timeout, callback) -> # noop
+
+ write: (data) ->
+ combinedBuffer = new Buffer(@writeBuffer.length + data.length)
+ @writeBuffer.copy(combinedBuffer)
+ combinedBuffer.write(data.toString(), @writeBuffer.length)
+ @writeBuffer = combinedBuffer
+ @emit('write')
+
+exports.FakeSocket = FakeSocket
42 lib/m2node/fake_socket.js
@@ -0,0 +1,42 @@
+(function() {
+ var FakeSocket, events, sys, util;
+ var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
+ for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
+ function ctor() { this.constructor = child; }
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor;
+ child.__super__ = parent.prototype;
+ return child;
+ };
+ sys = require('sys');
+ util = require('util');
+ events = require('events');
+ FakeSocket = (function() {
+ __extends(FakeSocket, events.EventEmitter);
+ function FakeSocket() {
+ this.writeBuffer = new Buffer('');
+ this.writable = true;
+ }
+ FakeSocket.prototype.destroy = function() {};
+ FakeSocket.prototype.destroySoon = function() {};
+ FakeSocket.prototype.emitData = function(buffer) {
+ if (this._events && this._events['data']) {
+ this.emit('data', buffer);
+ }
+ if (this.ondata) {
+ return this.ondata(buffer, 0, buffer.length);
+ }
+ };
+ FakeSocket.prototype.setTimeout = function(timeout, callback) {};
+ FakeSocket.prototype.write = function(data) {
+ var combinedBuffer;
+ combinedBuffer = new Buffer(this.writeBuffer.length + data.length);
+ this.writeBuffer.copy(combinedBuffer);
+ combinedBuffer.write(data.toString(), this.writeBuffer.length);
+ this.writeBuffer = combinedBuffer;
+ return this.emit('write');
+ };
+ return FakeSocket;
+ })();
+ exports.FakeSocket = FakeSocket;
+}).call(this);
27 lib/m2node/handler.coffee
@@ -0,0 +1,27 @@
+events = require 'events'
+zeromq = require 'zeromq'
+
+{MongrelRequest} = require './mongrel_request'
+
+class Handler extends events.EventEmitter
+ constructor: (options) ->
+ @pullSocket = zeromq.createSocket('pull')
+ @pullSocket.connect(options.recv_spec)
+ @pullSocket.on 'message', (message) =>
+ @emit 'request', new MongrelRequest(message)
+
+ @pubSocket = zeromq.createSocket('pub')
+ @pubSocket.connect(options.send_spec)
+
+ sendResponse: (request, response) ->
+ header = [
+ request.uuid, ' ',
+ request.connectionId.length, ':', request.connectionId,
+ ', '
+ ].join('')
+ outBuffer = new Buffer(header.length + response.length)
+ outBuffer.write(header, 'ascii')
+ response.copy(outBuffer, header.length)
+ @pubSocket.send(outBuffer)
+
+exports.Handler = Handler
36 lib/m2node/handler.js
@@ -0,0 +1,36 @@
+(function() {
+ var Handler, MongrelRequest, events, zeromq;
+ var __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) {
+ for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; }
+ function ctor() { this.constructor = child; }
+ ctor.prototype = parent.prototype;
+ child.prototype = new ctor;
+ child.__super__ = parent.prototype;
+ return child;
+ }, __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
+ events = require('events');
+ zeromq = require('zeromq');
+ MongrelRequest = require('./mongrel_request').MongrelRequest;
+ Handler = (function() {
+ __extends(Handler, events.EventEmitter);
+ function Handler(options) {
+ this.pullSocket = zeromq.createSocket('pull');
+ this.pullSocket.connect(options.recv_spec);
+ this.pullSocket.on('message', __bind(function(message) {
+ return this.emit('request', new MongrelRequest(message));
+ }, this));
+ this.pubSocket = zeromq.createSocket('pub');
+ this.pubSocket.connect(options.send_spec);
+ }
+ Handler.prototype.sendResponse = function(request, response) {
+ var header, outBuffer;
+ header = [request.uuid, ' ', request.connectionId.length, ':', request.connectionId, ', '].join('');
+ outBuffer = new Buffer(header.length + response.length);
+ outBuffer.write(header, 'ascii');
+ response.copy(outBuffer, header.length);
+ return this.pubSocket.send(outBuffer);
+ };
+ return Handler;
+ })();
+ exports.Handler = Handler;
+}).call(this);
27 lib/m2node/mongrel_request.coffee
@@ -0,0 +1,27 @@
+class MongrelRequest
+ constructor: (messageBuffer) ->
+ message = messageBuffer.toString()
+ [@uuid, @connectionId, @path, headersAndBody] = @_splitString(message, ' ', 4)
+ [rawHeaders, bodyNS] = @_parseNetstring(headersAndBody)
+ [@body] = @_parseNetstring(bodyNS)
+ @headers = JSON.parse(rawHeaders)
+
+ toFullHttpRequest: ->
+ request = []
+ request.push @headers.METHOD + ' ' + @path + ' HTTP/1.1\r\n'
+ for k, v of @headers
+ request.push "#{k}: #{v}\r\n"
+ request.push "\r\n"
+ request.push @body
+ new Buffer(request.join(''))
+
+ _parseNetstring: (netstring) ->
+ [length, data] = @_splitString(netstring, ':', 2)
+ [data.slice(0, parseInt(length)), data.slice(parseInt(length) + 1)]
+
+ _splitString: (string, delimiter, limit) ->
+ result = string.split(delimiter)
+ result.slice(0, limit - 1).concat([result.slice(limit - 1).join(delimiter)])
+
+
+exports.MongrelRequest = MongrelRequest
38 lib/m2node/mongrel_request.js
@@ -0,0 +1,38 @@
+(function() {
+ var MongrelRequest;
+ MongrelRequest = (function() {
+ function MongrelRequest(messageBuffer) {
+ var bodyNS, headersAndBody, message, rawHeaders, _ref, _ref2;
+ message = messageBuffer.toString();
+ _ref = this._splitString(message, ' ', 4), this.uuid = _ref[0], this.connectionId = _ref[1], this.path = _ref[2], headersAndBody = _ref[3];
+ _ref2 = this._parseNetstring(headersAndBody), rawHeaders = _ref2[0], bodyNS = _ref2[1];
+ this.body = this._parseNetstring(bodyNS)[0];
+ this.headers = JSON.parse(rawHeaders);
+ }
+ MongrelRequest.prototype.toFullHttpRequest = function() {
+ var k, request, v, _ref;
+ request = [];
+ request.push(this.headers.METHOD + ' ' + this.path + ' HTTP/1.1\r\n');
+ _ref = this.headers;
+ for (k in _ref) {
+ v = _ref[k];
+ request.push("" + k + ": " + v + "\r\n");
+ }
+ request.push("\r\n");
+ request.push(this.body);
+ return new Buffer(request.join(''));
+ };
+ MongrelRequest.prototype._parseNetstring = function(netstring) {
+ var data, length, _ref;
+ _ref = this._splitString(netstring, ':', 2), length = _ref[0], data = _ref[1];
+ return [data.slice(0, parseInt(length)), data.slice(parseInt(length) + 1)];
+ };
+ MongrelRequest.prototype._splitString = function(string, delimiter, limit) {
+ var result;
+ result = string.split(delimiter);
+ return result.slice(0, limit - 1).concat([result.slice(limit - 1).join(delimiter)]);
+ };
+ return MongrelRequest;
+ })();
+ exports.MongrelRequest = MongrelRequest;
+}).call(this);
19 package.json
@@ -0,0 +1,19 @@
+{
+ "name" : "m2node",
+ "version" : "0.1.0",
+ "description" : "mongrel2 handler",
+ "keywords" : ["http", "mongrel2"],
+ "homepage" : "https://github.com/dan-manges/m2node",
+ "author" : "Dan Manges <dan.manges@gmail.com> (http://www.dan-manges.com)",
+ "main" : "./lib/m2node.js",
+ "repository" : { "type": "git", "url" : "https://github.com/dan-manges/m2node.git" },
+ "engines" : { "node": "~0.4.8" },
+ "bundledDependencies" : [ "" ],
+ "dependencies" : {
+ "zeromq": "~0.5.1"
+ },
+ "devDependencies" : {
+ "coffee-script": "~1.1",
+ "vows": "=0.5.8"
+ }
+}
1  spec/.gitignore
@@ -0,0 +1 @@
+config.sqlite
37 spec/app.coffee
@@ -0,0 +1,37 @@
+http = require 'http'
+m2node = require '../lib/m2node'
+
+server = http.createServer((req, res) ->
+ console.log("#{req.method} #{req.url}")
+ switch req.url
+ when '/'
+ res.writeHead(200, {'Content-Type': 'text/plain'})
+ res.end('Hello World\n')
+ when '/echo_headers'
+ res.writeHead(200, {'Content-Type': 'text/plain'})
+ res.end(JSON.stringify(req.headers))
+ when '/set_response_header'
+ res.statusCode = 200
+ res.setHeader('X-CustomResponseHeader', 'm2node')
+ res.end('OK')
+ when '/echo_body'
+ body = ''
+ req.on('data', (data) ->
+ body += data
+ )
+ req.on('end', ->
+ res.statusCode = 200
+ res.end(body)
+ )
+ else
+ res.writeHead(404, {})
+ res.end("Could not find page: #{req.url}")
+)
+
+m2node.run(
+ server,
+ send_spec: 'tcp://127.0.0.1:9996'
+ recv_spec: 'tcp://127.0.0.1:9997'
+)
+console.log('Ready...')
+
2  spec/logs/.gitignore
@@ -0,0 +1,2 @@
+*.log
+
114 spec/m2node_spec.js
@@ -0,0 +1,114 @@
+var vows = require('vows');
+var assert = require('assert');
+var http = require('http');
+
+vows.describe('m2node').addBatch({
+ 'smoke test': {
+ topic: function () {
+ var callback = this.callback;
+ var req = http.request({
+ host: 'localhost',
+ port: 9000,
+ method: 'GET',
+ path: '/'
+ }, function (response) {
+ response.on('data', function (chunk) {
+ callback(null, {
+ status: response.statusCode,
+ headers: response.headers,
+ body: chunk
+ })
+ });
+ });
+ req.end();
+ },
+
+ 'is successful': function (err, response) {
+ assert.equal(response.status, '200');
+ assert.equal(response.body.toString(), 'Hello World\n');
+ }
+ },
+
+ 'request headers': {
+ topic: function () {
+ var callback = this.callback;
+ var req = http.request({
+ host: 'localhost',
+ port: 9000,
+ method: 'GET',
+ path: '/echo_headers',
+ headers: {'X-Testing': 'm2node'}
+ }, function (response) {
+ response.on('data', function (chunk) {
+ callback(null, {
+ status: response.statusCode,
+ headers: response.headers,
+ body: chunk
+ })
+ });
+ });
+ req.end();
+ },
+
+ 'are all passed to the server': function (err, response) {
+ assert.equal(response.status, '200');
+ assert.equal(JSON.parse(response.body.toString())['x-testing'], 'm2node');
+ }
+ },
+
+ 'response headers': {
+ topic: function () {
+ var callback = this.callback;
+ var req = http.request({
+ host: 'localhost',
+ port: 9000,
+ method: 'GET',
+ path: '/set_response_header'
+ }, function (response) {
+ response.on('data', function (chunk) {
+ callback(null, {
+ status: response.statusCode,
+ headers: response.headers,
+ body: chunk
+ })
+ });
+ });
+ req.end();
+ },
+
+ 'are all passed back': function (err, response) {
+ assert.equal(response.status, '200');
+ assert.equal(response.headers['x-customresponseheader'], 'm2node');
+ }
+ },
+
+ 'request body': {
+ topic: function () {
+ var callback = this.callback;
+ var postData = 'foo=bar&body_echoed=true';
+ var req = http.request({
+ host: 'localhost',
+ port: 9000,
+ method: 'POST',
+ path: '/echo_body',
+ headers: {'Content-Length': postData.length}
+ }, function (response) {
+ response.on('data', function (chunk) {
+ callback(null, {
+ status: response.statusCode,
+ headers: response.headers,
+ body: chunk
+ })
+ });
+ });
+ req.write(postData);
+ req.end();
+ },
+
+ 'are all passed back': function (err, response) {
+ assert.equal(response.status, '200');
+ assert.equal(response.body.toString(), 'foo=bar&body_echoed=true');
+ }
+ }
+}).export(module)
+
24 spec/mongrel2.conf
@@ -0,0 +1,24 @@
+m2node_example = Handler(
+ send_spec = 'tcp://127.0.0.1:9997',
+ send_ident = '81b7114c-534c-4107-9f17-b317cfd59f62',
+ recv_spec = 'tcp://127.0.0.1:9996',
+ recv_ident = ''
+)
+
+localhost = Host(name = 'localhost', routes = {
+ '/': m2node_example
+})
+
+main = Server(
+ name = "m2node_tests",
+ port = 9000,
+ uuid = '5dc1fbe7-d9db-4602-8d19-80c7ef2b1b11',
+ access_log = "/logs/access.log",
+ error_log = "/logs/error.log",
+ chroot = ".",
+ default_host = "localhost",
+ pid_file = "/run/mongrel2.pid",
+ hosts = [localhost]
+)
+
+servers = [main]
1  spec/run/.gitignore
@@ -0,0 +1 @@
+*.pid
1  spec/tmp/.git_empty_dir
@@ -0,0 +1 @@
+
Please sign in to comment.
Something went wrong with that request. Please try again.