Mounted Web Apps Sites

This weekend I got to go to Railscamp 6. Railscamp is so much fun. There’s heaps of really cool ruby peeps there. I’d start to name them but there were over 120 people at this one so it may not make for the most interesting read to those who weren’t there.

The reason I’m writing this post though is not about Railscamp itself, but rather the project that I helped hack on. Lincoln Stoll flew all the way from Germany to be at Railscamp. I love catching up with him and as usual, he had plenty of interesting stuff happening. One that really caught my attention was his idea of having a proxy through to CouchDB. We’d love to be able to use CouchDB in applications with some authentication and whilst the Couch team are working on this, we’d still like the auth to come from within the ruby application via Warden or something so we’re not doubling up on auth logic. It’d also be awesome if we can mount couchdb in my applications url space so that we can use couch directly from the browser via ajax in my main application. Something that I’ve actually wanted to do for a while.

So I started hacking on implementing this in Pancake while Lincoln got over his jet lag. When he got up he showed me what he’d done on the plane as a lambda based rack application which sorted the issues I was having, and between the two of use we had a Pancake stack that was proxying to couchdb in pretty short order.

ProxyStack was born.

The first thing to do is get ProxyStack 0.2.0 or greater. It’s on gemcutter so

gem install proxy_stack

should do the job. This should bring in Pancake if you don’t already have it too.

Lets make a simple couchdb proxy from a config.ru file.


echo "require 'rubygems'; require 'proxy_stack'; run ProxyStack.stackup" > config.ru
unicorn -p 5000

Navigate on over to http://localhost:5000/ and http://localhost:5000/_utils to see your couch app proxied through your rubies :D

That’s kinda cool. We could also mount it at a different url by setting up a container stack and mounting it in there.

require 'rubygems'
require 'proxy_stack'

class ::ProxyContainer < Pancake::Stacks::Short
  router.mount(ProxyStack, "/my_couch")
end

run ProxyContainer.stackup

Restart unicorn (or just use shotgun) and your couchdb is now available at http://localhost:5000/my_couch/ (note the trailing /)

You’ll see that when you mount it away from root, the /_utils resource doesn’t really work. That’s because couch doesn’t know it’s mounted and generates the urls without the mount path appended. That doesn’t matter for what I want though, which is accessing the database itself via json calls.

Now, say we want to augement that so that we have an “/admin/users” resource on our stack. Maybe we want to provide a signup form or something similar, or maybe a login form. You can just add actions to the stack like you normally would. For example, lets inherit the ProxyStack into our own stack, and make some tweaks:


require 'rubygems'
require 'proxy_stack'

class ::MyProxy  < ProxyStack
  get "/admin/users", :name => :admin_users do
    "You're in #{stack_class} at #{url(:admin_users)}"
  end

end

class ::ProxyContainer < Pancake::Stacks::Short

  router.mount(MyProxy, "/my_couch")
end

run ProxyContainer.stackup

You’ll notice that I’m prefixing the class definitions with “::”. This is to put them into the global space. When loading a rackup, they’re loaded into an anonymous class/module. You can get around that by not declaring classes directly in your config.ru file.

Now if you mosey on over to http://localhost:5000/my_couch/admin/users you’ll see the results of the new augmented action on the stack. Neat.

The other thing we wanted to be able to do is authenticate / authorize calls to couchapp from within our proxy. Lets say we’re using Warden for the authentication. We can use a before_proxy hook to add an authenticate! call, allowing a host application to specify what that means, and just using it in our proxy stack.


class ::MyProxy  :admin_users do
    "You're in #{stack_class} at #{url(:admin_users)}"
  end
end 

Here, we’ve added a before hook to show authentication via warden, and also to print out a little message to the log. The hook is run in the action context so it’s like we’re right inside the action. We’re not going to setup warden inside this article so we’ll leave it commented out. You can add as many before_proxy hooks, and after_proxy hooks as you like.

So now we’ve seen it as a raw rack app, inherited into a container app, augmented and authenticated. But what about actually using it in X% of ruby apps… Rails. Well, because it’s rack, you can use it in rails as a metal. Go on over to your application and generate a metal:

script/generate metal proxy_metal

Make sure to require proxy_stack in your environment in config.gem and setup your metal to look like this:

class ProxyMetal
  class MyProxy < ProxyStack
    before_proxy do
      puts "BEFORE PROXY FOR #{stack_class} with #{request.path_info}"
    end
  end

  class CouchProxy    < MyProxy; end

  class ProxyContainer < Pancake::Stacks::Short
    router do |r|
      r.mount(CouchProxy,   "/couchdb")
    end
  end

  def self.call(env)
    @app ||= ProxyContainer.stackup
    @app.call(env)
  end
end

Fire it up and head over to http://localhost:3000/couchdb/ and you’ve got your proxy from within rails. It’s important that you don’t mount it at “/” in rails, otherwise every request will first check couchdb before being passed onto your rails app!

But, why did I put so much cruft into the Rails Metal one? You could have setup the rails metal to be this


class ProxyMetal
  class ProxyContainer < Pancake::Stacks::Short; end
  ProxyContainer.mount(ProxyStack, "/couchdb")
  
  def self.call(env)
    @app ||= ProxyContainer.stackup
    @app
  end
end

but I wanted to demonstrate something else. Proxy stack it turns out, can proxy whatever http request you like. This is for demonstration purposes only however. Before you do this, get permission or own the other site. I take no responsibility for your actions if you decide to try and hijack content, which incidentally, would make you a complete douche bag.


class ProxyMetal
  class MyProxy < ProxyStack
    before_proxy do
      puts "BEFORE PROXY FOR #{stack_class} with #{request.path_info}"
    end
  end

  class CouchProxy    < MyProxy; end
  class TwitterProxy  < MyProxy
    configuration.proxy_domain = "twitter.com"
    configuration.proxy_port      = 80
  end

  class ProxyContainer < Pancake::Stacks::Short
    router do |r|
     r.mount(TwitterProxy, "/twitter_proxy")
     r.mount(CouchProxy,   "/couchdb")
    end
  end

  def self.call(env)
    @app ||= ProxyContainer.stackup
    @app.call(env)
  end
end

Now you have http://twitter.com mounted in your application at http://localhost:3000/twitter_proxy/

About these ads

7 Responses to “Mounted Web Apps Sites”

  1. Pancake: How To Stack and Loosely Couple Rack-Based Webapps Together Says:

    […] mountable in Rails 3. Pancake stacks can be used with the current versions of Rails via Metal, and examples are given on Pancake's blog for doing […]

  2. almost effortless » Weekly Digest, 12-11-00 Says:

    […] Mounted Web Apps Sites …and between the two of use we had a Pancake stack that was proxying to couchdb in pretty short order. […]

  3. Pancake - Como empilhar e acoplar aplicações baseadas em Rack Says:

    […] com Pancake podem ser utilizadas com as versões atuais do Rails através do Metal, veja exemplos de como fazê-lo no blog […]

  4. Geoffrey Grosenbach Says:

    I’m getting an error that says it can’t find usher/interface/rack_interface.rb. This file appears to be from an older version of the Usher router (0.5.10).

    Do you have plans to upgrade pancake and the proxy_stack to work with the current release of Usher? (I couldn’t find the include in either proxy_stack or pancake.)

  5. sukhchander Says:

    just write separate rack apps then proxy to them using your webserver of choice. i can’t even read that. imagine maintaining this in your code base.

    • hassox Says:

      By doing that you loose the ability to control the routing to seperate components of your application within the application, and also lose the ability to nicely generate routes. The configuration you’d have to maintain would be the nightmare for me. Monolithic applications, or reams of configuration are not my choice. But hey… that’s what it’s all about right!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: