<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[toon.io]]></title><description><![CDATA[Freelance JavaScript developer]]></description><link>http://toon.io/</link><generator>Ghost v0.4.2</generator><lastBuildDate>Sun, 05 Apr 2026 13:30:23 GMT</lastBuildDate><atom:link href="http://toon.io/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[Compile and serve go http apps]]></title><description><![CDATA[<p>When developing go web apps we need to recompile and re-serve the application to view the changes we made. This process of making changes, stop the app from running, recompile and run again can be quite annoying.</p>

<p>We need a tool that serves the app, notices our source code changing, automatically recompiles it, stops the previous build from being served to finally serve the new build.</p>

<p><a href='https://github.com/toonketels/crun' >CRUN - continuously Compile and Run -</a> does exactly that. It's a go CLI application. With one command all .go files from the current directory are being watched.</p>

<p>Similar tools already exist but I wanted something super simple to use with some tests.</p>

<h2 id="use">Use</h2>

<pre><code>// Change to directory with your app go source code
cd /path/to/my/app

// Start compiling and running app
crun
</code></pre>

<p>You can pass additional arguments to the binary.</p>

<pre><code>// additional arguments after `--` are passed to the binary
crun -- --port=:3000
</code></pre>

<h2 id="install">Install</h2>

<p>Go get the package to install</p>

<pre><code>go get github.com/toonketels/crun
</code></pre>]]></description><link>http://toon.io/compile-and-serve-go-http-apps/</link><guid isPermaLink="false">caf35f8d-a823-4bc6-ad3e-3ff46122b58b</guid><category><![CDATA[golang]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Sun, 16 Nov 2014 08:54:25 GMT</pubDate></item><item><title><![CDATA[Setup Karma to test Angular apps]]></title><description><![CDATA[<p>Last post we talked about how to <a href='http://toon.io/how-to-setup-karma-javascript-test-runner' >setup Karma with mocha and chai</a>. This post we see some particularities in setting up Karma to test Angular apps.</p>

<p>Since Karma loads the JavaScript files for us, we won't include an index.html to bootstrap the Angular app or to initialize some Angular modules. This is nice but at the same time poses a probem. By only loading the files, we can not use Angular's dependency injection system when testing code.</p>

<p>We use <code>angular-mocks</code> to circumvent this problem.</p>

<p>First, install angular mocks.</p>

<pre><code>bower install angular-mocks --save-dev
</code></pre>

<p>Modify <code>karma.config.js</code> to include Angular mocks.</p>

<pre><code>files: [
    "node_modules/chai/chai.js",
    "src/vendor/angular/angular.js",
    "src/vendor/angular-mocks/angular-mocks.js",
    "src/js/app.js",
    "tests/**/*.js"
],
</code></pre>

<p>Finally, ensure the module to test is ready by including the following in each test:</p>

<pre><code>beforeEach(angular.mock.module("APP"))
</code></pre>

<p>This will make the specific module ready to be tested. So all controllers and services defined on the module are available for testing.</p>

<p>You'll find an example on <a href='https://github.com/toonketels/karma-examples/tree/master/two' >github</a>.</p>]]></description><link>http://toon.io/setup-karma-test-angular-apps/</link><guid isPermaLink="false">4f5048ac-8dec-4f71-8978-8a9e18a05ca3</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Karma]]></category><category><![CDATA[Testing]]></category><category><![CDATA[Angular]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Mon, 25 Aug 2014 20:27:43 GMT</pubDate></item><item><title><![CDATA[How to setup Karma JavaScript test runner]]></title><description><![CDATA[<p><a href='http://karma-runner.github.io/' >Karma</a> is a test runner developed by the Angular team trying to bring a productive test environment to developers. As a test runner it allows us to run our <strong>client side JavaScript tests</strong> in <strong>real browsers</strong> from the <strong>command line</strong>.</p>

<p>Though we find plenty of information about Karma on their website, I found it not all obvious what the different configuration settings in its config file do. We try to explain this using an example project.</p>

<h2 id="javascriptprojectwithkarmamochaandchai">JavaScript project with Karma, mocha and chai</h2>

<p>The project has the following directory structure (<a href='https://github.com/toonketels/karma-examples/tree/master/one' >get if from github</a>):</p>

<pre><code>// Contains all source files
src/js/
src/vendor/

// Contains the tests
tests/unit/

// Contains nodejs modules
node_modules/
</code></pre>

<h2 id="installtestinglibs">Install testing libs</h2>

<p>First, install Karma and some plugins:</p>

<pre><code>// Install karma, mocha adapter and chai
npm install karma karma-mocha chai --save-dev
</code></pre>

<p>Everything is installed in the <code>node_modules</code> directory. Karma is the <strong>test runner</strong>. As a test runner it:</p>

<ul>
<li>Starts a webserver to serve our JavaScript source and test files</li>
<li>Loads all files in the correct order</li>
<li>Spins up browsers to run our tests in</li>
</ul>

<p><a href='http://visionmedia.github.io/mocha/' >Mocha</a> is a <strong>test framework</strong>. It is responsible to run our tests and report back which failed.</p>

<p><a href='http://chaijs.com/' >Chai</a> is an <strong>assertion library</strong>. It provides a nice api to check whether our code does the things we expect it to do.</p>

<h2 id="createkarmaconfigfile">Create Karma config file</h2>

<p>Next we create the Karma config file in our directory root:</p>

<pre><code>// Create Karma config file
./node_modules/karma/bin/karma init
</code></pre>

<p>This will prompt us for some questions and generates a config file. Items of interests to us are:</p>

<pre><code>basePath: '',
</code></pre>

<p>The base path tells Karma where to load our files from. Setting it to <code>''</code> means all files will be looked up from the root of our project.</p>

<pre><code>files: [
    "node_modules/chai/chai.js",
    "src/**/*.js",
    "tests/**/*.js"
],
</code></pre>

<p>Files tells Karma which files it should load relative to the base path. These are:</p>

<ul>
<li>All test related libraries</li>
<li>Our source code to test</li>
<li>The tests themselves</li>
</ul>

<p>Note we instruct Karma to load <code>chai.js</code>. Forgetting this means chai will not be loaded.</p>

<p>What about Mocha? Doesn't it need to be loaded too?</p>

<p>Yes it should. However, Karma will autoload all sibling node modules starting with <code>karma-</code>. This means if Karma is installed in the <code>node_modules</code> directory, all other modules starting with <code>karma-</code> will be autoloaded for us. And mocha is fetched as the <code>karma-mocha</code> node module. No need to manually load it ourselves.</p>

<pre><code>frameworks: ['mocha'],
</code></pre>

<p>Frameworks instructs Karma to use the mocha testing framework.</p>

<pre><code>browsers: ['Chrome'],
</code></pre>

<p>Browsers tells Karma to run the tests in Chrome. Note, Chrome needs to be installed on our computer. Running the tests from the command line will spin up a new Chrome window.</p>

<p>The <code>karma-chrome-launcher</code> node module needs to be installed too. <code>karma init</code> command will auto install it for us. If you add a browser later on, don't forget to install its runner too.</p>

<pre><code>port: 9876,
</code></pre>

<p>Karma will start its own server on this port to serve our JavaScript files and tests. The default is ok, only change it when we're already using that port.</p>

<pre><code>autoWatch: true,
</code></pre>

<p>Auto watch instructs Karma to rerun the tests whenever a file changes.</p>

<pre><code>singleRun: false
</code></pre>

<p>Single run tells Karma to shut down the browser when all tests have ran. This is useful for Continuous Integration systems where only one run is needed. For development, set it to false as this will keep the browser window opened. This means no time is wasted spinning up a new browser every time tests need to be executed.</p>

<h2 id="runthetests">Run the tests</h2>

<p>Now we can run the tests like:</p>

<pre><code>./node_modules/karma/bin/karma start
</code></pre>

<p>Notice we can also run the tests with <code>./node_modules/karma/bin/karma run</code>. This will not spin up the server to load our test files. Use it in combination with <code>karma start</code>. Start will spin up the browser and load the files, run will instruct karma to rerun the tests.</p>

<h2 id="usekarmacli">Use karma-cli</h2>

<p>It's tiresome to always do <code>./node_modules/karma/bin/karma start</code> instead of just <code>karma start</code>. Therefore install <code>karma-cli</code> globally.</p>

<pre><code>npm install -g karma-cli
</code></pre>

<p>This tool executes the Karma commands for the project in the current directory. By exposing the commands in a separate module then the Karma implementation, we can use different versions of Karma for different projects on our computer.</p>

<p>Note: we might have installed Karma globally before. If so, we need to delete it first as it will mess up which plugins it will autoload. For example: we might have installed <code>karma-mocha</code> in our project dir but not globally. Executing Karma (as global installed module) will fail to load <code>karma-mocha</code> (since "global" Karma will only look into the globally installed modules). Just uninstall <code>karma</code> as a global module and install <code>karma-cli</code> globally instead.</p>]]></description><link>http://toon.io/how-to-setup-karma-javascript-test-runner/</link><guid isPermaLink="false">cb8f96f5-c675-4a61-88f5-863bf057930b</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Karma]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Wed, 20 Aug 2014 18:22:34 GMT</pubDate></item><item><title><![CDATA[Using the go router package]]></title><description><![CDATA[<p>The <a href='https://github.com/toonketels/router' >router package</a> provides a simple URL router and HandlerFunc dispatcher for go web apps. This post explains how to use it.</p>

<p>In its simplest form, just create a router instance and register HandlerFuncs to HTTP verb/path pairs. Use the router by calling <code>Handle</code> and <code>ListenAndServe</code>. A working example is shown below.</p>

<pre><code>package main

import (  
    "github.com/toonketels/router"
    "net/http"
)

func main() {  
    // Create a new router
    appRouter := router.NewRouter()

    // Register a handlerFunc for GET/"hello" paths
    appRouter.Get("/hello", func(res http.ResponseWriter, req *http.Request) {
        res.Write([]byte("hello"))
    })

    // Use this router
    appRouter.Handle("/")

    // Listen for requests
    http.ListenAndServe(":3000", nil)
}
</code></pre>

<p>Now, let's dive a bit deeper in the package API.</p>

<h3 id="handlerfuncs">HandlerFuncs</h3>

<p>Once a router instance has been created, use its <code>Get/Put/Post/Patch/Head/Options/Delete</code> methods to register a handlerFunc to a route/HTTP verb pair.</p>

<pre><code>// Register a handlerFunc for GET/"hello" paths
appRouter.Get("/hello", handlerFunc)

// Register a handlerFunc for PUT/"hello" paths
appRouter.Put("/hello", handlerFunc)

// Register a handlerFunc for POST/"hello" paths
appRouter.Post("/hello", handlerFunc)

// Register a handlerFunc for Patch/"hello" paths
appRouter.Patch("/hello", handlerFunc)

// Register a handlerFunc for HEAD/"hello" paths
appRouter.Head("/hello", handlerFunc)

// Register a handlerFunc for OPTIONS/"hello" paths
appRouter.Options("/hello", handlerFunc)

// Register a handlerFunc for DELETE/"hello" paths
appRouter.Delete("/hello", handlerFunc)  
</code></pre>

<p>You can also register multiple handlerFuncs for a given route.</p>

<pre><code>// Register the handlerFuncs
appRouter.Get("/user/:userid/hello", logUser, handleUser)  
</code></pre>

<p>For this to work, all handlerFuncs should pass control to handlerFuncs coming after them by calling <br />
<code>cntxt.Next()</code>.</p>

<pre><code>func loadUser(res http.ResponseWriter, req *http.Request) {

    // Grab the context for the current request
    cntxt := router.Context(req)

    // Do something

    // Pass over control to next handlerFunc
    cntxt.Next(res, req)
}
</code></pre>

<p>If your handlerFunc wants to protect access to certain routes, it could do so by only calling cntxt.Next() when some authorization rule validates.</p>

<pre><code>func allowAccess(res http.ResponseWriter, req *http.Request) {  
    if allowUserAccess() {
        // Allows access
        router.Context(req).Next(res, req)
        return
    }
    // Denies access
}
</code></pre>

<p>HandlerFuncs can store data onto the requestContext to be used by handlerFuncs after them.</p>

<pre><code>func loadUser(res http.ResponseWriter, req *http.Request) {  
    cntxt := router.Context(req)
    user := getUserFromDB(cntxt.Params["userid"])

    // Store the value in request specific store
    _ = cntxt.Set("user", user)

    cntxt.Next(res, req)
}
</code></pre>

<p>HandlerFuncs use cntxt.Get(key) to get the value. RequestContext has <code>Set/ForceSet/Get/Delete</code> methods all related to the data stored during the current request.</p>

<pre><code>func handleUser(res http.ResponseWriter, req *http.Request) {  
    cntxt := router.Context(req)

    // Get a value from the request specific store
    if user, ok := cntxt.Get("user"); ok {
        // Do something
    }
    // Do something else
}
</code></pre>

<p>Remember the route <code>/user/:userid/hello</code>? It matches routes like <code>/user/14/hello</code> or <code>/user/richard/hello</code>. HandlerFuncs can access the values of <code>userid</code> on the requestContext.</p>

<pre><code>func loadUser(res http.ResponseWriter, req *http.Request) {

    // Grab the userid param from the context
    userid := router.Context(req).Params["userid"]

    // Do something with it
}
</code></pre>

<p>As you might have noticed, handlers need to be http.HandlerFunc's. So you can use your existing ones if you don't need to access the requestContext.</p>

<h3 id="mountinghandlerfuncs">Mounting handlerFuncs</h3>

<p>Some handlerFuncs need to be executed for every request, like a logger. Instead of passing it to every registered route, we "mount" them using <code>router.Mount()</code>.</p>

<pre><code>appRouter := router.NewRouter()

// Mount handlerFuncs first
appRouter.Mount("/", logger)  
appRouter.Mount("/", allowAccess)

// Then start matching paths
appRouter.Get("/user/:userid/hello", loadUser, handleUser)  
</code></pre>

<p>The order in which you mounted and registered handlers, is the order in which they will be executed.</p>

<p>A request to <code>/user/14/hello</code> will result in <code>logger</code> to be called first, followed by <code>allowAccess</code>, <code>loadUser</code> and <code>handleUser</code>. That is as long as none of the handlerFunc's prevented the latter ones from executing by not calling next.</p>

<p>By changing the mountPath of allowAccess to <code>/admin</code>, we get different results.</p>

<pre><code>// Mount handlerFuncs first
appRouter.Mount("/", logger)  
appRouter.Mount("/admin", allowAccess)

// Then start matching paths
appRouter.Get("/user/:userid/hello", loadUser, handleUser)  
appRouter.Get("/admin/user/:userid", loadUser, administerUserHandler)  
</code></pre>

<p>It the above case a request to <code>/user/20/hello</code> will execute <code>logger -&gt; loadUser -&gt; handleUser</code>, while a request to <code>/admin/user/20</code> executes <code>logger -&gt; allowAccess -&gt; loadUser -&gt; administerUserHandler</code>.</p>

<p>We see that by dividing a complex handlerFunc into multiple smaller ones, we get more code reuse. It becomes easy to create a small set of "middleware" handlers to be reused on different routes. While the last handlerFunc is generally the one responsible for generating the actual response.</p>

<h3 id="errorhandling">Error Handling</h3>

<p>Besides storing data and dispatching the next handlerFunc cntxt has an <code>Error</code> method. Let's update the <code>loadUser</code> handlerFunc to take errors into account.</p>

<pre><code>func loadUser(res http.ResponseWriter, req *http.Request) {  
    cntxt := router.Context(req)
    user, err := getUserFromDB(cntxt.Params["userid"])
    if err != nil {

        // Let the errorHandlerFunc generate the error response.
        // We stop executing the following handlers
        cntxt.Error(res, req, err.Error(), 500)
        return
    }

    // Store the value in request specific store
    _ = cntxt.Set("user", user)

    // Pass over control to next handlerFunc
    cntxt.Next(res, req)
}
</code></pre>

<p>Calling <code>cntxt.Error()</code> notifies the requestContext an error has been made and further <code>Next()</code> call will be prevented. It delegates the requestHandling to a dedicated <code>errorHandlerFunc</code> to reply in a consistent manner. </p>

<p>Though calling <code>Next()</code> after an error will never dispatch the next HandlerFunc, it is wise to just return after the error so the current <br />
HandlerFunc stops executing.</p>

<p>Previous HandlerFuncs are allowed to continue executing their code when the executing flow returns to them.</p>

<p>For instance, when the logger below is called, it records the time and calls the next HandlerFunc. If that handler errs, logger will resume as usual allowing it to log its output. </p>

<pre><code>func logger(res http.ResponseWriter, req *http.Request) {

    // The fist handlerFunc to be executed
    // record the time when the request started
    start := time.Now()

    // Handle over control to the next handlerFunc.
    router.Context(req).Next(res, req)

    // We log once all other handlerFuncs are done executing
    // so it needs to come after our call to cntxt.Next()
    fmt.Println(req.Method, req.URL.Path, time.Since(start))
}
</code></pre>

<p>The actual response send to the client is handled by default ErrorRequestHandler. Which will just do <code>http.Error(res, err, code)</code>.</p>

<p>Customize the response by updating your router's <code>ErrorHandler</code>. The function passed should comply with the <code>ErrorHandler</code> interface.</p>

<pre><code>appRouter.ErrorHandler = func(res http.ResponseWriter, req *http.Request, err string, code int) {  
    http.Error(res, strings.ToUpper(err), code)
}
</code></pre>

<p>The request is passed to allow a different response to be send depending on request properties.</p>

<p>Similarly, configure the response generated when a route is not found by updating the router's <code>NotFoundHandler</code> which is a plain http.HandlerFunc.</p>

<h3 id="conclusion">Conclusion</h3>

<p>With just a couple of concepts: registering requestHandlers, mounting requestHandlers and a requestContext for each request, we can do powerful things.</p>

<p>You can find some <a href='https://github.com/toonketels/router' >examples in the projects repo</a>.</p>]]></description><link>http://toon.io/using-the-go-router-package/</link><guid isPermaLink="false">825e3327-8c95-46ba-8758-f627adb34247</guid><category><![CDATA[golang]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Sat, 16 Aug 2014 12:50:25 GMT</pubDate></item><item><title><![CDATA[URL router for golang webapps]]></title><description><![CDATA[<p>Last week I've created an URL router and HandlerFunc dispatcher for go (golang) webapps.</p>

<p>I considered the following ideas (heavily borrowing from express/connect):</p>

<ul>
<li>registering handlers based on HTTP verb/path combos should be easy, as this is most often used</li>
<li>split complex HandlerFuncs into multiple smaller one which can be shared</li>
<li>mount generic HandlerFuncs to be executed on every path</li>
<li>registering and accessing paths with params (like :userid) should be easy</li>
<li>store data on a requestContext, so it can be passed to later HandlerFuncs</li>
<li>set a generic errorHandlerFunc and stop executing later handerFuncs as soon as an error occurs</li>
<li>set a generic pageNotFound HandlerFunc</li>
<li>handlers are regular <code>http.HandlerFunc</code> to be compatible with go</li>
</ul>

<p>I've created the package mainly to get my feet wet in go. A couple of similar packages already exist, but writing my own made me more familiar with the language and its standard lib.</p>

<p>The package is <a href='https://drone.io/github.com/toonketels/router' >fully tested</a> and ready to be used. <a href='https://github.com/toonketels/router' >Code</a> can be found on github, <a href='http://godoc.org/github.com/toonketels/router' >documentation</a> on godoc.</p>]]></description><link>http://toon.io/url-router-for-golang-webapps/</link><guid isPermaLink="false">6c299252-3665-48f1-8723-849c1293e081</guid><category><![CDATA[golang]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Mon, 11 Aug 2014 14:00:32 GMT</pubDate></item><item><title><![CDATA[Configuring haproxy to load balance multiple engine.io servers]]></title><description><![CDATA[<p><a href='http://haproxy.1wt.eu/' >Haproxy</a> is a Load Balancer to forward traffic to multiple servers. It works with HTTP and plays well with WebSockets.</p>

<p><a href='https://github.com/LearnBoost/engine.io' >Engine.io</a> is <em>the implementation of transport-based cross-browser/cross-device bi-directional communication layer for Socket.IO</em>. It's WebSockets with fallbacks to make it work on the web.</p>

<p>Engine.io is much more low level then socket.io. It leaves things like reconnects and <a href='http://en.wikipedia.org/wiki/Multiplexing' >multiplexing</a> up to us to implement. As such, it's similar to <a href='https://github.com/sockjs/sockjs-node' >sock.js</a>.</p>

<p>Because it starts with long-polling and does not try to do too many things, we can optimise real time connections to our needs, making it a better fit to run in production compared to socket.io.</p>

<h2 id="background">Background</h2>

<p>We have separated our app into an httpServer and socketServer (powered by engine.io) allowing us to spin up more instances of each depending on the load.</p>

<p>For this to work, we need two things:</p>

<ul>
<li>a Load Balancer to forward all incoming traffic to httpServer instances or socketServer instances</li>
<li>a shared session store so each server instance can access the same session information</li>
</ul>

<p>Happroxy does the loadbalancing. We use connect-redis and connect-sessions to share session information.</p>

<h2 id="theproblem">The problem</h2>

<p>HttpServers and socketServers (with fallbacks) are two different beasts.</p>

<p>HttpServers can/should be stateless. This means that <strong>any request can be handled by any httpServer</strong>. It does not matter where the request lands.</p>

<p>The socketServers on the other hand requires <strong>all connections from one client to land on the same socketServer</strong>. This includes regular HTTP requests used to establish the initial connection and when using long-polling, FlashSocket and WebSocket connections.</p>

<p>If a WebSocket connection would downgrade to long-polling and HTTP requests would be routed to random servers, engine.io would just not work.</p>

<h2 id="haproxy">Haproxy</h2>

<p>Our haproxy config needs to do two things:</p>

<ul>
<li>find out which traffic (HTTP and sockets) needs to be routed to socketServers and which to the httpServers</li>
<li>ensure all engine.io related connections from one client always land on the same socketServer instance</li>
</ul>

<h3 id="haproxyconfigfile">Haproxy config file</h3>

<p>Example haproxy config file:</p>

<pre><code class="`">defaults  
    mode    http
    retries 3
    # option redispatch
    timeout connect  5000
    timeout client  10000
    timeout server  10000


# Application can reached on port 3000
# Load balancer will split traffic to http
# and socket servers based on /engine.io prefix.
frontend all 127.0.0.1:3000  
    mode http
    timeout client 120s

    option forwardfor
    # Fake connection:close, required in this setup.
    option http-server-close
    option http-pretend-keepalive

    acl is_engineio path_beg /engine.io

    use_backend socket-servers if is_engineio
    default_backend http-servers


backend http-servers  
    balance roundrobin
    option httpclose
    option forwardfor

    # Roundrobin switching
    server node-1 127.0.0.1:4000 check
    server node-2 127.0.0.1:4001 check


backend socket-servers  
    timeout server  120s
    balance leastconn

    # based on cookie set in header
    # haproxy will add the cookies for us
    cookie SRVNAME insert
    server node-1 127.0.0.1:5000 cookie S1 check
    server node-2 127.0.0.1:5001 cookie S2 check
</code></pre>

<p>`</p>

<h3 id="notes">Notes</h3>

<pre><code>acl is_engineio path_beg /engine.io

use_backend socket-servers if is_engineio
default_backend http-servers
</code></pre>

<p>The above instructs haproxy to make a distinction for regular HTTP requests and socket related requests based on the <code>/engine.io</code> path of the request.</p>

<pre><code>balance roundrobin
</code></pre>

<p>For the httpServers, we use a roundrobin strategy to distribute requests to different servers. Basically, we alternate the servers to send a requests to.</p>

<pre><code>server node-1 127.0.0.1:4000 check
server node-2 127.0.0.1:4001 check
</code></pre>

<p>We use two httpServers, listening on ports 4000 and 4001.</p>

<pre><code>balance leastconn
</code></pre>

<p>We use a least connection strategy for engine.io traffic. This is <a href='http://cbonte.github.io/haproxy-dconv/configuration-1.4.html' #balance">better for long lasting connections</a>. Haproxy selects the server with the least amount of connections to route new connections to (if no earlier connection from that client was made).</p>

<pre><code>cookie SRVNAME insert
</code></pre>

<p>This part is an important directive to make engine.io work. We use a technique called <strong>sticky sessions</strong> to route all request from the one client to the same server. It instructs haproxy to issue a cookie for each connection to differentiate servers. It will take this cookie into account to route incoming requests from a client that already made requests before to the same socketServer.</p>

<pre><code>server node-1 127.0.0.1:5000 cookie S1 check
server node-2 127.0.0.1:5001 cookie S2 check
</code></pre>

<p>Our socketServers listen on port 5000 and 5001.</p>

<h2 id="resources">Resources</h2>

<ul>
<li><a href='https://github.com/LearnBoost/engine.io' >engine.io</a></li>
<li><a href='http://haproxy.1wt.eu/' >haproxy</a></li>
<li><a href='http://cbonte.github.io/haproxy-dconv/configuration-1.4.html' >haproxy documentation</a></li>
<li><a href='https://github.com/sockjs/sockjs-node/blob/master/examples/haproxy.cfg' >sock.js example haproxy config</a></li>
</ul>]]></description><link>http://toon.io/configuring-haproxy-multiple-engine-io-servers/</link><guid isPermaLink="false">57df696e-a69e-4ed7-8472-fc87e91b3dcb</guid><category><![CDATA[node.js]]></category><category><![CDATA[engine.io]]></category><category><![CDATA[haproxy]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Wed, 12 Mar 2014 11:00:00 GMT</pubDate></item><item><title><![CDATA[On passport.js, specific use cases]]></title><description><![CDATA[<p><a href='http://toon.io/articles/understanding-passportjs-authentication-flow' >In the previous post</a>, I talked about the authentication flow and the flow for subsequent requests using passportjs. This post will cover some specific use cases.</p>

<h2 id="whatuserinformationtostoreinthesessionandwhatnot">What user information to store in the session and what not?</h2>

<p>It's best to keep the session information small and only attach user information you actually need (note: some user info should be kept - like for instance the user ID, otherwise passport cannot use its Session Strategy).</p>

<p>However, if you use the <code>deserializeUser</code> method to go and load the user from the db based on the user ID attached to the session, it's better to store the entire user object in the session. This will prevent a roundtrip to the database on every request just to fetch user information.</p>

<h2 id="howtosplituppassportjsconfigurationbetweenmultiplefiles">How to split up passportjs configuration between multiple files?</h2>

<p><a href='https://github.com/jaredhanson/passport-local/blob/master/examples/express3/app.js' >In the example</a>, all password configuration and Local Strategy definition was specified in the main <code>app.js</code> file.</p>

<p>The passport instance we get back from <code>require('passport')</code> is a singleton. So we can just configure express to <em>use the passport middleware</em> in <code>app.js</code> while <em>configuring passport</em> in another file. They all need to <code>require('passport')</code>.</p>

<p>There's no need to pass the password instance around.</p>

<h2 id="whatifmyformelementsarenamedemailandpasswordinsteadofusernameandpassword">What if my form elements are named email and password instead of username and password?</h2>

<p>Configure which values passport should use from the request body by the options object passed while configuring the Local Strategy.</p>

<pre><code>passport.use(new LocalStrategy(
    {
        usernameField: 'email',
        passwordField: 'password'   
    },
    function verify(email, password, done) {

        findByEmail(email, function(err, user) {
            if (err) return done(err);
            if (!user) return done(null, false);
            passwordHash.compare(password, user.password, function(err, res) {
                if (err) return done(err);
                if (!res) return done(null, false);
                done(null, user);
            });
        });
    }
));
</code></pre>

<h2 id="whatifidontlikehowpassportauthenicateredirectstheuserbutwanttorespondwithajsonresponse">What if I don't like how passport.authenicate redirects the user but want to respond with a JSON response?</h2>

<p>Don't use <code>passport.authenticate</code> as middleware but invoke it directly in the request handler. We need to manually call <code>login</code> for this to work.</p>

<pre><code class="`">app.post('/login', function handleLocalAuthentication(req, res, next) {

    passport.authenticate('local', function(err, user, info) {
        if (err) return next(err);
        if (!user) {
            return res.json(403, {
                message: "no user found"
            });
        }

        // Manually establish the session...
        req.login(user, function(err) {
            if (err) return next(err);
            return res.json({
                message: 'user authenticated',
            });
        });

    })(req, res, next);
};
</code></pre>

<h2 id="howtorestrictaccesstocertainpages">How to restrict access to certain pages?</h2>

<p>Just define your own middleware to be invoked on certain routes to restrict access. </p>

<pre><code>// Define it
module.exports = function restrict(req, res, next) {
    if (req.isUnAuthenticated()) return req.json(403, {message: 'Access denied, please log in'});

    next();
}
</code></pre>

<p>Use it when setting up your routes.</p>

<pre><code>// Use the restrict middleware
app.get('/route-with-restricted-access', restrict, function(res, res, next) {
    // Handle request...
});
</code></pre>

<p>If the user object contains role information, we could make additional checks on which role a user has and depending on that restrict access. </p>]]></description><link>http://toon.io/on-passportjs-specific-use-cases/</link><guid isPermaLink="false">b5e9452e-6be8-43d1-b135-b213e60b9dee</guid><category><![CDATA[passport.js]]></category><category><![CDATA[express.js]]></category><category><![CDATA[node.js]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Tue, 05 Nov 2013 11:00:00 GMT</pubDate></item><item><title><![CDATA[Understanding passport.js authentication flow]]></title><description><![CDATA[<p>Passport.js is a flexible authentication middleware (allowing users to log in) that can be fully customised and works great with connect/express.</p>

<p>It is flexible in the sense that it allows for different authentication strategies (think via Twitter, via our own user database - installed via separate modules that can be combined) and it allows us to specify any route or output during authentication.</p>

<p>The <a href='https://github.com/jaredhanson/passport-local' >Local Strategy</a> allows us to authenticate users by looking up their data in the app's database. It has some <a href='https://github.com/jaredhanson/passport-local/blob/master/examples/express3/app.js' >great examples how to use it</a>.</p>

<p>In this post, we walk through the authentication flow, the next post discusses some <a href='http://toon.io/articles/on-passportjs-specific-use-cases' >partical use cases using passportjs</a>.</p>

<h2 id="differentpartsinusingpassportjs">Different parts in using passport.js</h2>

<p>There are three main parts in using passport.js:</p>

<ul>
<li>Requiring the module and using its <code>passport.initialize()</code> and <code>passport.session()</code> middleware with express.</li>
<li>Configuring passport with at least one Strategy and setting up passport's <code>serializeUser</code> and <code>deserializeUser</code> methods.</li>
<li>Specifying a route which uses the <code>passport.authenticate</code> middleware to actually authenticate a user.</li>
</ul>

<p>The example clearly demonstrates the different items. We wont go over it again.</p>

<h2 id="authenticationrequestflow">Authentication request flow</h2>

<p>With the tree parts configured as in the example, the following happens when a user tries the authenticate via the <code>/login</code> route:</p>

<ol>
<li>When the user submits the login form, a <code>POST</code> request to <code>/login</code> is made resulting in the execution of the <code>passport.authenticate</code> middleware we've set up.  </li>
<li>As the authenticate middleware for that route is configured to handle the <code>local</code> strategy, passport will invoke our implementation of the local strategy.  </li>
<li>Passport takes the <code>req.body.username</code> and <code>req.body.password</code> and passes it to our verification function in the local strategy.  </li>
<li>Now we do our thing: loading the user from the database and checking if the password given matches the one in the database.  </li>
<li>In case of an Error interacting with our database, we need to invoke <code>done(err)</code>. When we cannot find the user or the passwords do not watch, we invoke <code>done(null, false)</code>. If everything went fine and we want the user to login we invoke <code>done(null, user)</code>.  </li>
<li>Calling <code>done</code> will make the flow jump back into <code>passport.authenticate</code>. It's passed the error, user and additional info object (if defined).  </li>
<li>If the user was passed, the middleware will call <code>req.login</code> (a passport function attached to the request).  </li>
<li>This will call our <code>passport.serializeUser</code> method we've defined earlier. This method can access the user object we passed back to the middleware. It's its job to determine what data from the user object should be stored in the session. The result of the <code>serializeUser</code> method is attached to the session as <code>req.session.passport.user = { // our serialised user object // }</code>.  </li>
<li>The result is also attached to the request as <code>req.user</code>.  </li>
<li>Once done, our requestHandler is invoked. In the example the user is redirected to the homepage.</li>
</ol>

<h2 id="subsequentauthenticatedrequestsflow">Subsequent authenticated requests flow</h2>

<p>On subsequent request, the following occurs:</p>

<ol>
<li>Express loads the session data and attaches it to the req. As passport stores the serialised user in the session, the serialised user object can be found at <code>req.session.passport.user</code>.  </li>
<li>The general passport middleware we setup (<code>passport.initialize</code>) is invoked on the request, it finds the <code>passport.user</code> attached to the session. If is doesn't (user is not yet authenticated) it creates it like <code>req.passport.user = {}</code>.  </li>
<li>Next, <code>passport.session</code> is invoked. This middleware is a Passport Strategy invoked on every request. If it finds a serialised user object in the session, it will consider this request authenticated.  </li>
<li>The <code>passport.session</code> middleware calls <code>passport.deserializeUser</code> we've setup. Attaching the loaded user object to the request as <code>req.user</code>.</li>
</ol>

<h2 id="summarypassportmethodsandmiddleware">Summary passport methods and middleware</h2>

<ul>
<li><code>passport.initialize</code> middleware is invoked on every request. It ensures the session contains a <code>passport.user</code> object, which may be empty.</li>
<li><code>passport.session</code> middleware is a Passport Strategy which will load the user object onto req.user if a serialised user object was found in the server.</li>
<li><code>passport.deserializeUser</code> is invoked on every request by <code>passport.session</code>. It enables us to load additional user information on every request. This user object is attached to the request as <code>req.user</code> making it accessible in our request handling.</li>
<li>Our Local Strategy is only invoked on the route which uses the <code>passport.authenticate</code> middleware.</li>
<li>Only during this authentication <code>passport.serializeUser</code> is invoked allowing us the specify what user information should be stored in the session.</li>
</ul>

<h2 id="overviewpassportmethodsattachedtotherequest">Overview passport methods attached to the request</h2>

<p>To finish an overview of passport methods accessible within request handlers:</p>

<ul>
<li><code>req.login()</code></li>
<li><code>req.logout()</code></li>
<li><code>req.isAuthenticated()</code></li>
<li><code>req.isUnAuthenticated()</code></li>
</ul>]]></description><link>http://toon.io/understanding-passportjs-authentication-flow/</link><guid isPermaLink="false">1cbcf03c-ce17-4319-9271-b99528ed7b7f</guid><category><![CDATA[passport.js]]></category><category><![CDATA[express.js]]></category><category><![CDATA[node.js]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Fri, 01 Nov 2013 11:00:00 GMT</pubDate></item><item><title><![CDATA[Get to know backbone.js, before it becomes just another pain in the #!$]]></title><description><![CDATA[<p>I'll be presenting about Backbone.js again to the Drupal community tomorrow morning at DrupalCampLeuven. My talk <a href='http://leuven2013.drupalcamp.be/session/get-know-backbonejs-it-becomes-just-another-pain' >'Get to know backbone.js, before it becomes just another "pain in the #!$"'</a> addresses the need in the Drupal community to get up to speed about Backbone.js.</p>

<p>Drupal 8 will ship with backbone.js and although backbone is just a tiny library that makes it much easier to structure JavaScript code, it does make the code more complex. It simplifies maintainability but at the same time adds a layer of complexity by adding new abstractions and rules about organising JavaScript code. It is easy to work with as long as we have an understanding of these rules and concepts. To prevent it being "a pain in the #!$", we should get to know Backbone.</p>

<p>As I'm scheduled first thing Sunday morning, I hope people make it that early...</p>]]></description><link>http://toon.io/get-to-know-backbonejs-before-it-becomes-just-another-pain/</link><guid isPermaLink="false">3a38b0fb-643f-48fb-a812-bbc2380b799a</guid><category><![CDATA[talks]]></category><category><![CDATA[backbone.js]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Sat, 14 Sep 2013 10:00:00 GMT</pubDate></item><item><title><![CDATA[DUG about Backbone.js]]></title><description><![CDATA[<p>Already introduced Backbone.js to Dutch Drupal developers. Today organized a Drupal User Group to talk about backbone.js to Belgian Drupal devs. Fortunately, we could spend a bit more time on the topic. Really enjoyed the talk. Audience asked lots of interesting questions.</p>

<p>Thanks to <a href='https://twitter.com/sdecabooter' >Sven Decabooter</a> for recording the talk and <a href='http://wunderkraut.be/' >Wunderkraut</a> for hosting the event.</p>

<p>Recording (in Dutch) and more info can be found on the <a href='http://drupal.be/evenement/dug-introducing-backbonejs-manage-javascript-application-complexity' >drupal.be community website</a>.</p>]]></description><link>http://toon.io/dug-backbonejs/</link><guid isPermaLink="false">5049eaa6-ffe8-4d03-adbb-4c9f77933aed</guid><category><![CDATA[talks]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Thu, 11 Jul 2013 10:00:00 GMT</pubDate></item><item><title><![CDATA[Introducing d3.js at fronteers meetup]]></title><description><![CDATA[<p>I presented about d3.js at a <a href='http://fronteers.nl/bijeenkomsten/2013/thomas-more' >fronteers meetup</a> in Mechelen.</p>

<p>The talk is a practical d3.js introduction to teach some of d3's fundamentals such as data-joins, scales, axis, labels, ticks, animations...</p>

<p>We started off with a plain SVG representing a chart, refine it, in each iteration touching a new topic to actually update the chart (animate bars and scale) every x minutes.</p>

<p>Take a look at the <a href='https://speakerdeck.com/toonketels/introducing-d3-dot-js' >presentation slides</a> and the <a href='https://github.com/toonketels/d3-presentation-demo' >demo code</a>.</p>

<h2 id="resources">Resources</h2>

<ul>
<li><a href='https://speakerdeck.com/toonketels/introducing-d3-dot-js' >Presentation slides</a></li>
<li><a href='https://github.com/toonketels/d3-presentation-demo' >Presentation demo code</a></li>
<li><a href='http://fronteers.nl/bijeenkomsten/2013/thomas-more' >Fronteers announcement</a></li>
</ul>]]></description><link>http://toon.io/d3js-presentation-fronteers-meetup/</link><guid isPermaLink="false">df11d515-40ed-4828-ab50-ff99ec27ad4a</guid><category><![CDATA[talks]]></category><category><![CDATA[d3.js]]></category><category><![CDATA[fronteers]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Thu, 23 May 2013 10:00:00 GMT</pubDate></item><item><title><![CDATA[Backbone.js, no more JavaScript spaghetti code]]></title><description><![CDATA[<p>As backbone.js is committed to drupal 8, I gave an introduction talk about backbone at drupalJam in the the Netherlands.</p>

<p>It's all about Backbone.js fundamentals with models, collections, views and their interaction (change event - rendering), to really understand what's going and how it helps manage JavaScript application complexity.</p>

<p>Checkout the <a href='https://speakerdeck.com/toonketels/introducing-backbone-dot-js' >presentation slides</a> and <a href='https://github.com/toonketels/backbone-presentation-demo' >demo code</a>.</p>]]></description><link>http://toon.io/backbonejs-no-more-javascript-spaghetti-code/</link><guid isPermaLink="false">bb8bb627-8d26-4296-89d8-39c051ef6a9e</guid><category><![CDATA[talks]]></category><category><![CDATA[backbone.js]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Fri, 17 May 2013 10:00:00 GMT</pubDate></item><item><title><![CDATA[Introducing Grunt.js, the JavaScript Task Runner]]></title><description><![CDATA[<p>Together with <a href='https://twitter.com/anthonyringoet' >Anthony Ringoet</a>, I presented about <a href='http://gruntjs.com/' >Gruntjs</a> at the <a href='http://lanyrd.com/2013/njug-be/schedule' >Node.js User Group</a> in Antwerp.</p>

<p>We gave a practical introduction to using Grunt.js. Once the basics (installing, using) were adressed, we took on some more advanced, in depth topics such as tasks, "magic" config attributes and project scaffolding.</p>

<p>Checkout the <a href='https://speakerdeck.com/toonketels/introducing-grunt-dot-js-the-javascript-task-runner' >presentation slides</a> and the <a href='https://github.com/toonketels/grunt-presentation-demo' >demo code</a>.</p>]]></description><link>http://toon.io/introducing-gruntjs-the-javascript-task-runner/</link><guid isPermaLink="false">397c192c-65bf-45c3-b9c1-b4e052043e50</guid><category><![CDATA[talks]]></category><category><![CDATA[grunt.js]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Wed, 15 May 2013 10:00:00 GMT</pubDate></item><item><title><![CDATA[Corelio annual report with backbonejs and reponsive d3js charts]]></title><description><![CDATA[<p>I was asked by <a href='http://wolfslittlestore.be/' >Johan from Wolf's Little Store</a> to help creating some d3js charts for <a href='http://corelio.be/bedrijfsinfo/jaarverslagen/jv2012' >Corelio's 2012 Annual Report</a>.</p>

<p>We ended up creating a backbone.js application to display the slides touching on different topics.</p>

<h2 id="someofthechallenges">Some of the challenges</h2>

<ul>
<li>Display the chart axis on page load, but only start displaying the chart's content (graphs) when within the viewport.</li>
<li>Don't start showing the chart's content when the visitor is scrolling very fast over them.</li>
<li>Initially, the charts had a fixed width. We ended up making the charts responsive by adapting to the available width.</li>
<li>The visitor can scroll to specific slides using the menu.</li>
<li>Create a static one pager with a JavaScript application to make everything interactive.</li>
<li>Make parts of the d3.js charts reusable since we created several of them.</li>
</ul>

<h2 id="someofthetoolsused">Some of the tools used</h2>

<ul>
<li><a href='http://backbonejs.org/' >Backbone.js</a>: to structure the application and keep us sane</li>
<li><a href='http://d3js.org/' >D3.js</a>: to create the different charts</li>
<li><a href='http://imakewebthings.com/jquery-waypoints' >jQuery waypoints</a>: to decide when to display the chart's contents and figure out which menu link to activate</li>
<li><a href='http://gruntjs.com/' >Gruntjs</a>: to help us build the endresult, which is basically one static file</li>
<li><a href='http://requirejs.org/' >require.js</a>: to load the different JavaScript files and divide the JavaScript application in different modules</li>
</ul>

<p>Checkout the <a href='http://corelio.be/bedrijfsinfo/jaarverslagen/jv2012' >endresult</a>.</p>]]></description><link>http://toon.io/corelio-annual-report-with-backbonejs-and-reponsive-d3js-charts/</link><guid isPermaLink="false">e1e8586e-2eee-437b-b602-b45fb5545f09</guid><category><![CDATA[backbone.js]]></category><category><![CDATA[d3.js]]></category><category><![CDATA[projects]]></category><category><![CDATA[grunt.js]]></category><category><![CDATA[require.js]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Sun, 12 May 2013 10:00:00 GMT</pubDate></item><item><title><![CDATA[Extend Fail2ban to send text messages every time a user/bot gets banned]]></title><description><![CDATA[<p>To prevent scripts from brute forcing their way into my VPS via SSH I use <a href='http://www.fail2ban.org/' >Fail2ban</a>. It's a tool that checks the logs for password failures and set IP rules that blocks that user's IP for a given time.</p>

<p>I have it already set up to send emails (one of the default actions), but I wanted it to send me a text message every time an IP gets banned.</p>

<p>Fail2ban does not ship with such an action. Fortunately, it's not hard to create one.</p>

<h2 id="howtoextendfail2banwithcustomactions">How to extend fail2ban with custom actions?</h2>

<p>It's easy to extend Fail2ban with custom actions. You only need to do two things: <br />
1. Create a new action in the /etc/fail2/ban/actions.d directory <br />
2. Configure jail.local to use that action</p>

<h2 id="createnewactioninactionsd">Create new action in actions.d</h2>

<p>The easiest way to create a new action is to copy dummy.conf to <em>new-action.conf</em> and edit it. You'll find the different "hooks" you can use to execute your commands. Variables are passed via "&lt;variable>" syntax.</p>

<p>Below you'll find my sms.conf.</p>

<pre><code>#
# Author: Toon Ketels
#
# $Revision$
#

[Definition]

# Option:  actionstart
# Notes.:  command executed once at the start of Fail2Ban.
# Values:  CMD
#
actionstart = /home/all/scripts/fail2ban-sms.sh start

# Option:  actionstop
# Notes.:  command executed once at the end of Fail2Ban
# Values:  CMD
#
actionstop = /home/all/scripts/fail2ban-sms.sh stop

# Option:  actioncheck
# Notes.:  command executed once before each actionban command
# Values:  CMD
#
actioncheck =

# Option:  actionban
# Notes.:  command executed when banning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    &lt;ip&gt;  IP address
#          &lt;failures&gt;  number of failures
#          &lt;time&gt;  unix timestamp of the ban time
# Values:  CMD
#
actionban = /home/all/scripts/fail2ban-sms.sh ban &lt;ip&gt;

# Option:  actionunban
# Notes.:  command executed when unbanning an IP. Take care that the
#          command is executed with Fail2Ban user rights.
# Tags:    &lt;ip&gt;  IP address
#          &lt;failures&gt;  number of failures
#          &lt;time&gt;  unix timestamp of the ban time
# Values:  CMD
#
actionunban = /home/all/scripts/fail2ban-sms.sh unban &lt;ip&gt;

[Init]

init = 'Custom startup message
</code></pre>

<p>Above you see we don't actually send text messages directly from within this file, but invoke the script located at /home/all/scripts/fail2ban-sms.sh. We use the &lt;ip> variable to be send in the text message and pass it as an argument to the script. </p>

<p>Using scripts makes it easier to change the texting implementation later on (or when we would like to change provider).</p>

<p>Before we look into the text messaging script, let's activate the command.</p>

<h2 id="configfaillocaltoexecuteaction">Config fail.local to execute action</h2>

<p>Configure Fail2ban by copying jail.conf to jail.local and edit the file. We want texting whenever someone tries to bruteforce ssh. So we add "sms" to the list of actions in the ssh section like below.</p>

<pre><code>#
# in /etc/fail2ban/jail.local.
#
# Optionally you may override any other parameter (e.g. banaction,
# action, port, logpath, etc) in that section within jail.local

[ssh]

enabled  = true
port     = ssh
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 6
action = mail-whois[name=SSH, dest=my.email@gmail.com]
         sms
</code></pre>

<h2 id="textingusingtwilio">Texting using twilio</h2>

<p><a href='https://www.twilio.com/' >Twilio's API</a> makes it easy to send sms's. Just POST to their api to text. Below you find the script used to perform the request to their API.</p>

<pre><code>#!/bin/bash

# Sends text messages using twilio api
# to alert webmaster of banning.
#
# Requires one argument, one of the following:
#  start
#  stop
#  ban
#  unban
#
# Optional second argument: IP for ban/unban



# Include file with the following content:
#   sid=&lt;twilio account sid&gt;
#   token=&lt;twilio auth token&gt;
#   from=&lt;phone number&gt;
#   to=&lt;phone number&gt;
source secret.conf



# Display usage information
function show_usage {
  echo "Usage: $0 action &lt;ip&gt;"
  echo "Where action is start, stop, ban, unban"
  echo "and ip is optional passed to ban, unban"
  exit
}



# Actually send sms
# Expects the sms content (body) to be passed
# as argument.
function send_sms {
  /usr/bin/curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$sid/SMS/Messages.json" -d "From=$from" -d "To=$to" -d "Body=$1" -u "$sid:$token" &gt;&gt; '/home/all/log/fail2ban-sms.log'    
  exit
}



# Check for script arguments
if [ $# -lt 1 ]
then
  show_usage
fi



# Take action depending on argument
if [ "$1" = 'start' ]
then
  message='Fail2ban+just+started.'
  send_sms $message
elif [ "$1" = 'stop' ]
then
  message='Fail2ban+just+stopped.'
  send_sms $message
elif [ "$1" = 'ban' ]
then
  message=$([ "$2" != '' ] &amp;&amp; echo "Fail2ban+just+banned+$2" || echo 'Fail2ban+just+banned+an+ip.' )
  send_sms $message
elif [ "$1" = 'unban' ]
then
  message=$([ "$2" != '' ] &amp;&amp; echo "Fail2ban+just+unbanned+$2" || echo "Fail2ban+just+unbanned+an+ip." )
  send_sms $message
else
  show_usage
</code></pre>

<p>The script expects a secret.conf in the same directory to read the Twilio authentication data and phone numbers from. The output of the requests are appended to a logfile in /home/all/log/fail2ban-sms.log.</p>

<h2 id="repo">Repo</h2>

<p>You find the config file and script in <a href='https://github.com/toonketels/fail2ban-sms' >github repo</a>.</p>

<h2 id="resources">Resources</h2>

<ul>
<li><a href='http://www.fail2ban.org/' >www.fail2ban.org</a></li>
<li><a href='https://www.twilio.com/' >www.twilio.com</a></li>
<li><a href='https://github.com/toonketels/fail2ban-sms' >github.com/toonketels/fail2ban-sms</a></li>
<li><a href='http://blog.redbranch.net/2013/02/28/fail2ban-custom-action' >blog.redbranch.net/2013/02/28/fail2ban-custom-action</a></li>
</ul>]]></description><link>http://toon.io/fail2ban-send-sms-user-banned/</link><guid isPermaLink="false">6e793d55-f7a6-4f58-9652-df54c28b4324</guid><category><![CDATA[twillio]]></category><category><![CDATA[fail2ban]]></category><dc:creator><![CDATA[Toon Ketels]]></dc:creator><pubDate>Sat, 23 Mar 2013 11:00:00 GMT</pubDate></item></channel></rss>