Aaron Lerch

Thoughts from a perpetual student.

Preventing Rack::Session on a Per-Request Basis

This is admittedly a rare need, but the other day I found myself needing to conditionally enable and disable sessions in Sinatra on a per-request basis. Sinatra uses the Rack::Session middleware to manage sessions. I searched around for a while but couldn’t find out how to do it. There are options for the session that can be set per-request, like this:

1
2
3
get '/' do
  request.session_options[:renew] = true
end

The documentation shows a :defer option that seems to do what I want, but the terminology didn’t make it abundantly clear whether it did or not.

:defer will not set a cookie in the response.

Actually that does sound like what I want, sorta, but mentally I was thinking “turn off” or “disabling” and not “deferring the setting of a cookie.”

With most ruby projects, or for that matter, any interpreted language that isn’t compiled , I’ve found that I end up spending a decent amount of time reading the code from my dependencies. Some unscientific polling of ruby devs I know showed that to be a pretty standard practice.

Doing a little digging through the Rack::Session middleware showed an option that didn’t appear in the documentation:

:skip will not a set a cookie in the response nor update the session state

Now that matches the mental model of what I was looking for, and the behavior as well. Turns out it’s not in the docs because it was added recently from a Rails core team member to support the new asset pipeline in Rails 3.1, and the docs haven’t been updated yet.

So if, on a per-request basis, you want to disable using sessions entirely, simply do:

1
request.session_options[:skip] = true unless use_session?

(where “use_session?” is your method that figures out whether or not to use sessions)

What’s the diff between :defer and :skip, you ask? Well the answer is in the pull request description

This will not send a cookie back nor change the session state.

The :defer option did not send the cookie back but did change the session state in the backend.

This is useful for assets requests that still go through the rack stack but do not want to cause any change in the session (for example accidentally expiring flash messages).

App Extensibility, Follow Up

My last post about introducing an extensibility point got some good feedback. I realized two things:

  1. Following the pattern of rails generators was huge overkill
  2. I wasn’t thinking correctly about the problem to begin with

Rails generators need to support a lot of things. For example, they need to process command line arguments, consume or produce templates, and update existing files in a non-destructive way. The potential complexity in generators is handled by the complexity in the extensility model implemented in rails. Arbitrarily duplicating this complexity (magic class names, inheritance, etc) is just wrong. Which, of course, is why I said:

OK, I’m doing something wrong, I just know it

If I was going to continue down this road (I’m not, see below) I would take a cue from something like Sinatra and take advantage of global methods to reduce/remove requirements on the configuration code.

1
2
3
configure do |host|
  # do something with 'host' to configure your app
end

But that’s all irrelevent. Jeff Lindsay set me straight in a comment on my previous post.

The hooks that most SCM use, like post-commit or post-receive, are based on running shell scripts. This is my favorite approach so far because it’s not language specific.

DUH. I love those things that I look back and think “of course.”

I was thinking about this as a ruby problem, because localtunnel’s client is written in ruby. But it’s not a ruby problem. Localtunnel is a utility. I can write hooks for Git in anything I want, it doesn’t matter what language Git is written in. The same is true for localtunnel.

Taking this approach greatly simplifies the solution. And the simpler solution is always always always the better solution. In this case, we can add a quick check followed by a system call to enable users to provide a shell hook to configure whatever system(s) they want:

1
2
3
4
5
6
7
8
9
10
11
SHELL_HOOK_FILE = "./.localtunnel_callback"

...

if File.exists?(File.expand_path(SHELL_HOOK_FILE))
  system "#{SHELL_HOOK_FILE} ""#{tunnel['host']}""" if File.exists?(File.expand_path(SHELL_HOOK_FILE))
  if !$?.success?
    puts "   An error occurred executing the callback hook #{SHELL_HOOK_FILE}"
    puts "   (Make sure it is executable)"
  end
end

Bam. Localtunnel now provides an extensibility point that can be implemented in any language. Well, it will when/if my pull request is accepted. :)

Thanks, Jeff!

App Extensibility in Ruby

I ran into an interesting problem recently. There is a utility called localtunnel which connects a public URL to a local port. Extremely useful when it comes to developing an app that leverages services that expose web hooks.

The problem is that the public URL is only valid while the script is running. Which means every time you start it, you potentially need to go update the web hook URL in whatever service you’re using. For example, with Twilio you configure an app that is available at a particular URL. With GitHub you can add post-receive hooks.

What needs to happen is that localtunnel needs to give you a chance to run some custom code after it registers and connects up ports, but before it sits and waits.

It needs an extension point.

One fork seeks to do this by having localtunnel call a web hook. It works, even if it feels a bit ironic. ;) But that involves maintaining another service simply to configure your first service, which is less than ideal. I took a different approach and I’d love to get feedback here, since I’m still so new to this.

Most stuff on the web about extending ruby has to do with class design, or monkeypatching, or something else that assumes all the code is already loaded and executing. In this case, there’s an existing app (not framework) and I want to have it load my code dynamically at runtime. In my code, I want to configure whatever service I need to.

Being a n00b, I’m not the most widely read yet, so I looked at an example of something that I already knew exposed an extension point in a similar way: rails generators.

You can see my code here and the updated readme here, but here’s how it works.

Specify the config to run

This part was easy, I just extended the existing command line processing to add a “-c NAME” argument specifying which configuration you want to run. For example:

1
$ localtunnel -c twilio 9292

will create a public URL connecting to local port 9292, and will look for an auto configuration implementation named “twilio” and execute it.

Discovery via magic names, I mean, convention

This is a nice, but sometimes frustrating thing about Rails. There’s lots of stuff that magically happens if you organize or name your code a certain way.

In this case, to make it easy to specify via the command line, I opted for a similar “magic name” approach. Name your file foo_auto_config.rb and when you specify “-c foo” I’ll look for your file under a ‘localtunnel’ subdir. This is a way to try and avoid loading a lot of code unnecessarily.

Discover files by name link
1
2
3
4
5
6
7
8
9
10
11
12
13
def self.lookup(name)
  including_current = $LOAD_PATH.dup
  including_current << '.'
  including_current.each do |base|
    Dir[File.join(base, "localtunnel", "#{name}_auto_config.rb")].each do |path|
      begin
        require path
      rescue Exception => e
        puts "   [Warning] Could not load autoconfig #{path.inspect}. Error: #{e.message}.\n#{e.backtrace.join("\n")}"
      end
    end
  end
end

This has a few issues with it. Because this is running as a script, if you’re using bundler, the $LOAD_PATH won’t include all the gems unless Bundler.require has been called. So looking in the load path for auto configuration files is probably pointless. Not that gems would include a custom auto configuration anyway. I should probably remove this. (Thoughts?) Secondly, I manually added the local directory because ruby 1.9.2 took it out of $LOAD_PATH by default due to security reasons. And if you consider the use-case, this is typically executed from within ~/MyApp and not randomly throughout the directory structure. Again, similar to rails generators, you run it from the root of your application directory.

Discovery via class name + base class

Now that we’ve figured out which files to load, we still don’t know what code to call. We need to be able to pass in parameters such as the new URL that is reserved, so we probably want a method to call and not just loading a file.

Ruby gives us a feature that allows you to know when a new base class is created: the inherited method. It gets called when a new subclass is created.

I created a base class, LocalTunnel::AutoConfig::Base, which keeps track of all subclasses:

Collect all subclasses into an array link
1
2
3
4
5
6
7
8
9
class Base
  def self.inherited(base)
    super

    if base.name && base.name !~ /Base$/
      LocalTunnel::AutoConfig.subclasses << base
    end
  end
end

When someone specifies “-c foo” on the command line, I look for any subclass of LocalTunnel::AutoConfig::Base that is named FooAutoConfig and has a method called “configure”. If I find that, then that’s the auto configuration code that will be called.

Find the right class link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
def self.find(name)
  lookup(name)

  names = Hash[subclasses.map { |klass| [autoconfig_name(klass).downcase, klass] }]
  klass = names[name]
  return nil if klass.nil?

  configurator = klass.new
  if configurator.respond_to? :configure
    configurator
  else
    nil
  end
end

Then I can call that method on an instance of the matching class, and pass it the new URL.

Call the configuration class link
1
2
3
4
5
6
7
8
if !@autoconfig.nil?
  configurator = LocalTunnel::AutoConfig.find(@autoconfig)
  if configurator
    configurator.configure(tunnel['host'])
  else
    puts "   [Warning] Unable to find an automatic configuration plugin for '#{@autoconfig}'"
  end
end

Do your custom configuration

The configuration code can then run and do whatever it wants. Usually it’s specific per app. Here’s an example of me using this to configure Twilio.

twilio_auto_config.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
require 'rubygems'
require 'localtunnel/autoconfig'
require 'twilio-ruby'
require 'uri'

class TwilioAutoConfig < LocalTunnel::AutoConfig::Base

  TWILIO_ACCOUNT_SID = # my account Sid
  TWILIO_AUTH_TOKEN = ENV['TWILIO_AUTH_TOKEN']
  TWILIO_APP_SID = ENV['TWILIO_APP_SID']

  def configure(host)
    # set up a client to talk to the Twilio REST API
    client = Twilio::REST::Client.new(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
    app = client.account.applications.get(TWILIO_APP_SID)

    # Grab the current voice_url and status_callback and swap out the host
    voice = URI.parse(app.voice_url)
    voice.host = host
    status_callback = URI.parse(app.status_callback)
    status_callback.host = host

    app.update({:voice_url => voice.to_s, :status_callback => status_callback.to_s})
    puts "   Configured twilio app #{app.friendly_name} for new host #{host}"
  end
end

OK, I’m doing something wrong, I just know it

I am sure I either overcomplicated this, or just plain did it wrong. Some thoughts I have (in hindsight now) are that perhaps I could’ve exposed a global method for the autoconfig code to call. Then, instead of a base class and magic class name, I’d just load the specified file (magic name is still nice) and in that file I could call “host” to get the new host name.

Leave a comment, create a gist, fork my fork, or tweet at me, but somehow let me know how I could make this better.

N00b

It’s been a long time since I’ve spent significant time to learn an entirely new technology. And I’ve had a blast digging into ruby, rails, sinatra, padrino, javascript (okay, I really mean jQuery), and tons of other related frameworks. In spite of some of the excellent, and not-so-excellent, documentation out there, it can be an incredibly frustrating experience. Especially when trying to learn something new – like something really new – not just new to me. Expect to see posts here related to what I’m learning, that make as few assumptions as possible about what you already know.

For example, I recently worked on getting a test environment set up for my app that involves sinatra, mongoid, and twilio APIs. It was an interesting experience. I’m sure seasoned ruby devs wouldn’t have had the same issues, but I’m a firm believer in “you’re not alone” so I’ll be posting about the process to hopefully help someone else in my shoes.

New Content, New Blog

For the past 6 months to a year, I’ve been getting more and more into web development on the side (I used to be a web developer, a long time ago.) I’ve been focusing exclusively on ruby-based frameworks. It serves two purposes, I get to learn a new language, and there are plenty of options out there for me to explore.

In that spirit, instead of changing the focus of my existing blog, I’m going to take a cue from Ben Scheirman and start a new blog and just start updating this one more often. Which, given the number of things going on in my life right now, will not be often at all. :)

If you haven’t checked out octopress you should - it’s pretty sweet! I love the idea of a file-based blog engine using git.