Skip to content

Commit

Permalink
Initial commit of Slowloris
Browse files Browse the repository at this point in the history
  • Loading branch information
petehunt committed Sep 10, 2012
0 parents commit aad6a39
Show file tree
Hide file tree
Showing 6 changed files with 234 additions and 0 deletions.
56 changes: 56 additions & 0 deletions README.md
@@ -0,0 +1,56 @@
Slowloris
=========

Slowloris is a very simple web worker with the following goals:

+ Get non-UI logic out of the UI thread. This is often one of the main causes of underlying client-side performance problems.
+ Safety and simplicity: Normal use of Slowloris should not cause any concurrency nightmares (deadlock, race conditions, etc).
+ Legacy compatibility: Older browsers won't break and newer browsers will magically get faster.
+ Ignoring web workers: You shouldn't really need to write a web worker yourself unless you have a special use case.

I've included an example in the repo that has a bit of information about it.

Basically, you get an asynchronous eval() function. This is a pretty bad way to write real code; instead you should build a
better abstraction on top of Slowloris to offload as much as you can into the background thread and only do UI updates once
the computation is complete.

HOWTO
-----

See the example. Basically all you need to know is Loris.eval(expr, callback, errback); where expr is a string containing
the expression to evaluate, callback is the function that gets called with the result of the eval, and errback gets called
with any exceptions that may get thrown.

FAQ
---

+ The example isn't working.

Web workers don't work locally; use a web server. Read the paragraph in the example.

+ Why do I only get eval?

Because it's flexible enough to build a good abstraction on, but doesn't encourage you to actually do "real" concurrency.

+ Why do I only get 1 background thread?

Because the point of this is not to utilize multiple cores or be a great framework for building parallel apps, it's just
designed to get as much crap out of the UI thread as possible so you can build really responsive client-side apps in JS.

+ What should I avoid?

Just don't touch the underlying web worker stuff (Loris.worker or call any web worker stuff in the string you eval).

+ What's next?

I'd like to build an AMD module loader on top of this so you can just pass a module name and function name rather than a
string of JS to eval.

+ Why did you build this?

Because it's a shame that the web stack has such a bad reputation on mobile vs. native. Sure, native will probably always
beat web on benchmarks, but it shouldn't beat the web stack by as much as it has been. I think this is mostly due to
the fact that web front-end engineers put too much computation in the UI thread. There's a few other candiates too:
inefficient CSS (and JS that drives the CSS!) and poor network fetching/batching/caching of data and code. Hopefully
Slowloris solves the first problem, education and something like Zepto solves the second one, and something like Backbone
solves the third problem.
23 changes: 23 additions & 0 deletions example.css
@@ -0,0 +1,23 @@
#supported,
#time,
#status,
#computetime {
font-weight: bold;
}

#timeblock {
font-size: 24px;
}

.code {
font-family: monospace;
}

#localwarning {
color: red;
display: none;
}

.runninglocal #localwarning {
display: block;
}
42 changes: 42 additions & 0 deletions example.html
@@ -0,0 +1,42 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<title>Pointless Slowloris demo</title>
<script type="text/javascript" src="slowloris.js"></script>
<script type="text/javascript" src="http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.1.min.js"></script>
<script type="text/javascript" src="slowloris-example.js"></script>
<link rel="stylesheet" href="example.css" />
</head>

<body>
<h1>Slowloris demo</h1>
<p id="localwarning"><b>Web workers aren&apos;t available for local files!</b>
Please load this from a web server (http:// or https:// URL), not locally (file:// URL). An easy way to do this is
just running <span class="code">python -m SimpleHTTPServer</span> and visiting
<a href="http://localhost:8000/example.html">http://localhost:8000/example.html</a>.
</p>
<p>
This is an interactive demo of how Slowloris can improve your application performance.

I&apos;ve written a simple clock to represent an interactive UI.

And I&apos;ve also written a terrible for loop that blocks the UI thread. This represents &quot;computation&quot;.

I apologize if this messes up your browser. In fact, if you&apos;re using a browser that doesn't support web workers
you are probably already screwed; sorry!

Also look out, this spins your CPU and could kill your battery life on a laptop or mobile device.

Once you disable web workers, notice how choppy the clock updates itself. Voila!
</p>
<p id="timeblock">The current time is: <span id="time">&nbsp;</span>.</p>
<p>Last result computed at: <span id="computetime">never</span>.</p>
<p>Web workers <span id="supported">&nbsp;</span> supported by this browesr.</p>
<p>Web workers are <span id="status">&nbsp;</span>.</p>
<p><a href="javascript:;" id="toggle">Toggle web workers</a>.</p>
</body>
</html>
36 changes: 36 additions & 0 deletions slowloris-example.js
@@ -0,0 +1,36 @@
$(document).ready(function() {
// Set up event handlers and init the UI
$('#toggle').click(function() {
if (Loris.usingWebWorkers()) {
Loris.disableWebWorkers();
} else {
Loris.enableWebWorkers();
}
$('#status').text(Loris.usingWebWorkers() ? 'enabled' : 'disabled');
});
$('#supported').text(Loris.hasWebWorkers() ? 'are' : 'are not');
$('#status').text(Loris.usingWebWorkers() ? 'enabled' : 'disabled');
if (window.location.href.substring(0, 4).toLowerCase() !== 'http') {
$('body').addClass('runninglocal');
}

function tick() {
// Update the clock and run some asynchronous computation.
$('#time').text(new Date());
Loris.eval('var sum = 0; for (var i = 0; i < 10000000; i++) { sum++; }; [sum, new Date];', function (result) {
// Let's pretend we actually do something with the result here. We'll check its value here to
// prove that it's working, and update the UI to prove that we can update it.
var sum = result[0];
var date = result[1];
if (sum !== 10000000) {
$('#computetime').text('there was an error :(');
} else {
$('#computetime').text(date);
}
});
}
tick();

// Keep the clock updated
setInterval(tick, 500);
});
7 changes: 7 additions & 0 deletions slowloris-webworker.js
@@ -0,0 +1,7 @@
self.onmessage = function (event) {
try {
self.postMessage([event.data[0], true, eval(event.data[1])]);
} catch (e) {
self.postMessage([event.data[0], false, e]);
}
};
70 changes: 70 additions & 0 deletions slowloris.js
@@ -0,0 +1,70 @@
window.Loris = (function() {
var hasWebWorkers = typeof(Worker) !== 'undefined';
var Loris = function() {
this.callbacks = {};
this.errbacks = {}
this.counts = 0;
this.worker = null;
this.useWebWorkers = hasWebWorkers;
if (hasWebWorkers) {
try {
this.worker = new Worker('slowloris-webworker.js');
} catch (e) {
hasWebWorkers = false;
this.useWebWorkers = false;
return;
}
this.worker.onmessage = (function (event) {
var id = event.data[0];
var isException = event.data[1];
var payload = event.data[2];

if (isException) {
this.callbacks[id](payload);
} else {
this.errbacks[id](payload);
}
delete this.callbacks[id];
delete this.errbacks[id];
}).bind(this);
this.worker.onerror = function(event) {
// This should never actually happen but let's log it anyway
console.log('Slowloris uncaught exception: ', event.data);
};
}
};

Loris.prototype.eval = function (expr, callback, errback) {
if (!this.useWebWorkers) {
// TODO: explore trampolining this eval with setTimeout().
try {
callback(eval(expr));
} catch (e) {
errback(e);
}
return;
}
var id = this.counts++;
this.callbacks[id] = callback;
this.errbacks[id] = errback;
this.worker.postMessage([id, expr]);
};

Loris.prototype.disableWebWorkers = function() {
this.useWebWorkers = false;
};

Loris.prototype.enableWebWorkers = function() {
this.useWebWorkers = hasWebWorkers;
};

Loris.prototype.usingWebWorkers = function() {
return this.useWebWorkers;
};

Loris.prototype.hasWebWorkers = function() {
return hasWebWorkers;
};

return new Loris();
})();

0 comments on commit aad6a39

Please sign in to comment.