How One Pull Request Took App::HTTPThis to Version 1.0

I *heart* http_this

Back in January I wrote about App::HTTPThis, the tiny web server I seem to reach for whenever I need to share the contents of a directory over HTTP. It’s a deliberately simple tool, wrapping Plack::App::DirectoryIndex in a command-line interface that gets out of your way.

This week, thanks to a blog post from Olaf Alders, I discovered that it wasn’t behaving quite the way I thought it was.

Well… not quite. It wasn’t behaving the way I used it.

And that led to three releases in quick succession.

It wasn’t really serving “localhost”

When you started http_this, it printed something like this:

Like most people, I took that at face value. If a program tells me it’s serving on localhost, I naturally assume it’s only accessible from my own machine.

In reality, that wasn’t always what it was doing.

To be clear, http_this has long had a --host option. If you explicitly supplied a host address, everything behaved exactly as you’d expect—it binds to the named network interface. The surprising behaviour only occurred when --host was omitted—which, as it happens, is exactly how I always used the tool.

In that case, the server actually listened on every network interface (0.0.0.0). On many machines, a firewall would prevent anyone else from connecting, so most users would never notice. But the output was misleading. The software was saying one thing while doing another.

Olaf spotted this, wrote about it in his post Keep it Local, and submitted a pull request.

The first fix

Interestingly, Olaf’s pull request didn’t change the server’s behaviour. It simply made the output truthful.

If the server was listening on every interface, then the startup message should say so, together with a warning that other machines on the local network may be able to connect.

That was a worthwhile improvement in its own right.

But reviewing the change immediately prompted a more important question.

Should this really be the default?

Once the output accurately described the behaviour, I had to ask whether the behaviour itself was actually sensible.

For the vast majority of people, http_this is a quick way to preview a local website, browse some generated documentation, or inspect the output from a static site generator.

None of those use cases requires the server to be visible to every machine on the local network.

So in the next release, I changed the default. If you don’t specify --host, http_this now binds to 127.0.0.1.

If you genuinely want the previous behaviour, you can now ask for it explicitly using --all. That option also has an alias: --promiscuous.

I rather like that alias. It’s technically accurate, but it also makes you stop for a moment and think about whether exposing your server on every network interface is really what you intended.

To me, that’s a much better design. Software should generally do the safest reasonable thing unless the user explicitly asks for something different.

Breaking changes aren’t always bad

Changing a long-standing default like this is, by definition, a breaking change. Anyone who had come to rely on the previous behaviour would suddenly find that their workflow had changed.

That’s exactly what semantic versioning is for.

Rather than quietly slipping the change into a minor release, I bumped the major version number and released App::HTTPThis 1.0.0. The module has taken rather longer than most to reach a 1.0.0 release, but changing a long-established default seemed like an appropriate milestone.

I also made sure the documentation was very explicit about the behavioural change so existing users wouldn’t be caught by surprise.

(There’s now a 1.0.1 release too, but that’s just a small follow-up fix.)

Unfortunately, the first person I broke was me

I was feeling rather pleased with myself.

Then I tried using http_this from WSL.

One of the nice things about WSL is being able to run development tools under Linux while using a browser running on Windows. That’s a workflow I use every day.

Unfortunately, binding to 127.0.0.1 inside WSL isn’t quite the same thing as making the server available to Windows. By fixing the default behaviour, I’d accidentally made my own development workflow considerably less convenient.

Fortunately, I didn’t need to back out the security improvement.

Instead, I added a --wsl option (which can also be enabled in your configuration file). Rather than listening on every interface, it determines the appropriate WSL network address and binds specifically to that interface instead.

The result is that http_this now has three distinct modes:

  • the default, which binds only to 127.0.0.1;
  • --wsl, which binds to the interface Windows can actually reach; and
  • --all (or --promiscuous), which exposes the server on every network interface.

That feels like a much better balance between security and convenience.

A nice example of open source collaboration

One thing I particularly like about this sequence of events is how neatly it illustrates the way good open source software evolves.

Olaf didn’t arrive with a proposal to redesign the module. He noticed that the program’s output didn’t accurately describe what it was doing and submitted a focused pull request to correct that.

Reviewing his contribution naturally led me to question whether the underlying behaviour was actually the right default. Fixing that exposed an edge case in my own WSL workflow, which in turn led to a better solution for WSL users.

Looking back over the week, one small pull request ended up making the module more honest about what it was doing, safer by default, and more convenient to use in one of my favourite development environments.

Not bad for a week’s work.

Sometimes the best bug reports don’t just fix one bug. They make you realise there’s a better way for the software to behave.

Oh, and if reading this post makes you think I’m not really someone you wouldn’t want to trust with your network configuration—you’re probably right!

Further reading

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.