Permalink
Browse files

first

  • Loading branch information...
0 parents commit 6b976838a780123478f55dd13939e15eac767647 @broofa broofa committed Nov 18, 2010
@@ -0,0 +1,5 @@
+Proccoli is a lightweight proxy server, ala http://www.charlesproxy.com/
+
+To use, "node server.js"
+Configure your browser to use http://localhost:8888 as the http proxy
+Point your browser to http://localhost:8889 for a live view of all http traffic
@@ -0,0 +1,64 @@
+function LinkedList(data) {
+ this.first = null;
+ this.last = null;
+}
+LinkedList.prototype = {
+ _link: function linkit(before, item, after) {
+ if (before) {
+ before._after = item || after;
+ }
+ if (item) {
+ item._before = before;
+ item.after = after;
+ }
+ if (after) {
+ after._before = item || before;
+ }
+ if (!this.first) this.first = before || item || after;
+ if (!this.last) this.last = after || item || before;
+ },
+ push: function(item) {
+ var was = this.last;
+ this.last = null;
+ this._link(was, item);
+ return this;
+ },
+ pop: function() {
+ var item = this.last;
+ if (item) this.remove(item);
+ return item;
+ },
+ unshift: function(item) {
+ var was = this.first;
+ this.first = null;
+ this._link(null, item, was);
+ return this;
+ },
+ shift: function() {
+ var item = this.first;
+ if (item) this.remove(item);
+ return item;
+ },
+ remove: function(item) {
+ if (this.first == item) this.first = null;
+ if (this.last == item) this.last = null;
+ this._link(item._before, null, item._after);
+ delete item._before;
+ delete item._after;
+ return this;
+ },
+ size: function() {
+ for (var t = this.first, i = 0; t; t = t._after, i++);
+ return i;
+ },
+ forEach: function(f) {
+ var t = this.first, n;
+ while (t) {
+ var n = t._after;
+ f(t);
+ t = t._after;
+ }
+ }
+};
+
+module.exports = LinkedList;
@@ -0,0 +1,167 @@
+var http = require('http');
+var fs = require('fs');
+var querystring = require('querystring');
+var url = require('url');
+var mime = require('mime');
+var NodeStatic = require('node-static');
+var LinkedList = require('./lib/LinkedList');
+
+var proxyPort = 8888;
+var adminPort = 8889;
+
+console.log('Proxy @ http://localhost:' + proxyPort);
+console.log('Admin @ http://localhost:' + adminPort);
+
+// Process no die, por favor
+process.on('uncaughtException', function(e) {
+ console.log('Uncaught error: ' + util.inspect(e) + '\n' + e.stack);
+});
+
+//
+// The admin interface
+//
+
+// ReadyStates
+var UNSENT = 0;
+var OPENED = 1;
+var HEADERS_RECEIVED = 2;
+var LOADING = 3;
+var DONE = 4;
+
+var staticServer = new NodeStatic.Server('./webroot');
+
+var listeners = new LinkedList();
+
+// Move this to a prefs file?
+var filters = [
+ {
+ name: 'Filter template',
+ regex: /./,
+ beforeRequest: function(request, response) {
+ },
+ beforeResponse: function(request, response) {
+ response.headers['x-proccoli'] = 'heart healthy';
+ }
+ }
+];
+
+http.createServer(function(req, res) {
+ var parts = url.parse(req.url, true);
+ var query = parts.query = parts.query || {};
+
+ switch (parts.pathname) {
+ // Endpoint for pushing proxy activity back to client
+ case '/listen':
+ res.writeHead(200, {
+ 'Content-Type': 'octet/binary-stream',
+ 'Content-Encoding': 'chunked'
+ });
+ res.write('ready\n');
+ listeners.push(res);
+
+ // Clean up any listeners that go away
+ res.socket.on('end', function(e) {
+ listeners.remove(res);
+ });
+ break;
+
+ default:
+ // Everything else gets treated as a static file
+ staticServer.serve(req, res);
+ }
+}).listen(adminPort);
+
+//
+// The proxy interface
+//
+
+http.createServer(function(req, res) {
+ // Write the url to any listeners
+ var url = req.url.replace(/http:\/\/[^\/]*/, '');
+
+ // Assign a unique id
+ var uid = ((Math.random() * 0x7fffff) | 0).toString(36);
+ req.notify = function(msg) {
+ msg.uid = uid;
+ msg.ts = Date.now();
+
+ listeners.forEach(function(res) {
+ res.write(JSON.stringify(msg) + '\n');
+ });
+ };
+
+ req.notify({
+ url: req.url,
+ host: req.headers.host,
+ readyState: UNSENT
+ });
+
+ // Proxy the request
+ var client = http.createClient('80', req.headers.host);
+
+ // Client errors include host-not-found, etc.
+ client.on('error', function(err) {
+ console.log('client error - ' + err.message);
+ req.notify({readyState: DONE});
+ res.writeHead(400);
+ res.end(err.message);
+ });
+
+ var clientRequest = client.request(req.method, url, req.headers);
+ clientRequest.on('error', function(err) {
+ console.log('client request error (' + url + ') - ' + err.message);
+ });
+ clientRequest.end();
+
+ var clientResponse = null;
+ req.notify({readyState: OPENED});
+
+ // Clean up request and response
+ function cleanup() {
+ if (clientRequest) {
+ clientRequest = null;
+ }
+ if (clientResponse) {
+ clientResponse.end();
+ clientResponse = null;
+ }
+ }
+
+ clientRequest.on('response', function(ares) {
+ clientResponse = ares;
+ clientResponse.on('error', function(err) {
+ console.log('client response error (' + url + ') - ' + err.message);
+ });
+
+ req.notify({readyState: HEADERS_RECEIVED});
+
+ 'connect secure !data !end timeout drain !error close'.
+ split(' ').forEach(function(event) {
+ if (/!/.test(event)) return;
+ clientResponse.on(event, function() {
+ console.log(event + ' - ' + url);
+ });
+ });
+
+ // Apply response filters
+ filters.forEach(function(filter) {
+ if (filter.regex.test(req.url) && filter.beforeResponse) {
+ filter.beforeResponse(req, clientResponse);
+ }
+ });
+
+ res.writeHead(clientResponse.statusCode, clientResponse.headers);
+ req.notify({statusCode: clientResponse.statusCode});
+
+ clientResponse.on('data', function(chunk) {
+ req.notify({readyState: LOADING});
+ // TODO: We'd like to send this to listeners. but what's
+ // the best format... base64?
+ res.write(chunk);
+ });
+ clientResponse.on('end', function() {
+ req.notify({readyState: DONE});
+ res.end();
+ });
+ });
+}).listen(proxyPort);
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
@@ -0,0 +1,155 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+ body {
+ margin: 0px;
+ padding: 0px;
+ font: 10pt sans-serif;
+ }
+ H1 {
+ font-size: 12pt;
+ text-align: center;
+ margin: 0px;
+ padding: 4px;
+ color: #fff;
+ background-color: #050;
+ border-bottom: solid 1px #090;
+ }
+ #controls {
+ float: right;
+ border: solid 1px #ccc;
+ border-width: 0px 0px 1px 1px;
+ padding: 10px;
+ border-radius: 0px 0px 0px 8px;
+ background-color: #fff;
+ -webkit-box-shadow: 3px 3px 2px rgba(0,0,0,.4);
+ -moz-box-shadow: 3px 3px 2px rgba(0,0,0,.4);
+ }
+ UL {
+ margin: 0px;
+ padding: 0px;
+ margin-left: 1em;
+ }
+ LI {
+ margin: 0px;
+ padding: 1px 0px;
+ white-space: nowrap;
+ font: 10pt monospace;
+ list-style: none;
+ }
+ .host_requests LI {
+ font-size: 85%;
+ }
+ .hide_completed .host_requests LI {
+ display: none;
+ }
+ .host_requests LI.status_200 {color: #444;}
+ .host_requests LI.status_300 {color: #740;}
+ .host_requests LI.status_400 {color: #900;}
+ .host_requests LI.status_500 {color: #707;}
+ .host_requests LI.active {
+ display: block;
+ font-weight: bold;
+ background: url(images/loading.gif) no-repeat left center;
+ margin-left: -16px;
+ padding-left: 16px;
+ }
+ .host H2 {
+ cursor: pointer;
+ font-size: 12pt;
+ color: #666;
+ }
+ .host H2:hover {
+ color: #090;
+ }
+ .host LI {
+ }
+ .host.closed H2 {
+ font-weight: normal;
+ }
+ .host.closed LI {
+ display: none;
+ }
+ </style>
+ <head>
+<body>
+ <h1>Proccoli - A customizable web proxy for JS nerds</h1>
+ <div id="controls">
+ <button onclick="$('#hosts .host').remove()">Clear</button>
+ <br />
+ <label><input type="checkbox" onclick="$(document.body).toggleClass('hide_completed', this.checked)">Hide completed requests</label>
+ </div>
+
+ <ul id="hosts"></ul>
+
+ <!-- TEMPLATES -->
+ <div style="display:none">
+ <ul id="host_template" class="host">
+ <h2 class="host_name" onclick="toggle(this)">Host</h2>
+ <ul class="host_requests">
+ </ul>
+ </ul>
+
+ <script src="lib/jquery.min.js"></script>
+ <script src="lib/Dechunker.js"></script>
+ <script>
+ function toggle(el) {
+ var el = $(el);
+ el = el.closest('ul');
+ el.toggleClass('closed');
+ }
+
+ function init() {
+ var xhr = new XMLHttpRequest();
+ var dechunker = new Dechunker({
+ onMessage: function(msg) {
+ if (typeof(msg) == 'string') {
+ // Status messages. ignore for now.
+ } else {
+ var elId = 'el_' + msg.uid;
+ var el = $('#' + elId);
+
+ // Create elements needed to display the request
+ if (!el.length && msg.host && msg.url) {
+ // Create host list?
+ var hostId = msg.host.replace(/\W/g, '_');
+ var hostEl = $('#' + hostId);
+ if (!hostEl.length) {
+ hostEl = $('#host_template').clone();
+ hostEl.attr('id', hostId);
+ hostEl.children('h2').html(msg.host);
+ $('#hosts').append(hostEl);
+ }
+
+ // Create request item
+ var hostUL = hostEl.children('ul');
+ var el = $('<li id="' + elId + '">');
+ el.html(msg.url.replace(/[?#].*/, ''));
+ hostUL.append(el);
+ }
+
+ // Still no element? give up
+ if (!el.length) return;
+
+ if (msg.statusCode) {
+ var stat = 100*Math.floor(msg.statusCode/100);
+ el.get(0).className = 'status_' + stat;
+ }
+ el.toggleClass('active', msg.readyState < 4);
+ }
+ }
+ });
+ xhr.open('GET', '/listen');
+ xhr.onreadystatechange = function() {
+ dechunker.process(xhr);
+ if (xhr.readyState == 4) {
+ setTimeout(init, 1e3);
+ }
+ }
+ xhr.send();
+ }
+ setTimeout(init, 1e3);
+ </script>
+</body>
+</html>
Oops, something went wrong.

0 comments on commit 6b97683

Please sign in to comment.