Fixing Buggy Haskell Programs with Go

I recently ran into a stupid bug in a program written in Haskell and found it much easier to paper over with a few lines of Go than to properly fix.

Some readers may remember that a couple years ago I switched back to XMonad; as part of that switch I started using TaffyBar as a bit of persistent UI to keep my system tray, CPU usage and memory usage graphs, a couple weather widgets, and more.

For better or worse, the endpoints that all the weather widgets used changed a couple weeks ago when NOAA decided to switch their endpoints to HTTPS. Some programs probably were able to update trivially, either by simply adding an s to the URL scheme, or maybe less, by following a redirect.

Sadly, the Haskell program in question used an HTTP client that doesn’t support TLS out of the box. The upshot of this is that you need to do some relatively major surgery to update the program. I might consider that had I not had to deal with Haskell before; in my experience casually using Haskell is like giving yourself a tattoo. Painful, frustrating, and not very useful in the long run.

I had a conversation at work with my coworker, Joshua Pollack, and together we came to the conclusion that a much more sensible solution would be to just build a little proxy. In just under an hour I was able to get something put together! I made it yet another tool within my leatherman and creatively named it noaa-proxy.

For the record, here is all of the code:

package noaa

import (
	"io"
	"net/http"
	"net/http/httputil"
	"net/url"
)

var upstream *url.URL

func init() {
	// Manually resolved CNAME of tgftp.nws.noaa.gov
	u, err := url.Parse("https://tgftp.cp.ncep.noaa.gov")
	if err != nil {
		panic("Couldn't parse url: " + err.Error())
	}
	upstream = u
}

// Proxy starts a proxy that can pretend to be the old noaa on http while
// actually proxying to noaa on https.
func Proxy(_ []string, _ io.Reader) error {
	http.Handle("/", httputil.NewSingleHostReverseProxy(upstream))

	return http.ListenAndServe(":9090", nil)
}

Because the Haskell program doesn’t support proxies or anything, I had to hack this in with an entry in /etc/hosts. That was a pretty limiting factor here.

First I tried having it proxy to the IP address instead of another hostname. That didn’t work because TLS validation failed. Even by disabling validation, it still failed validation, because it finds the IP address in the cert before calling the validation function.

After that I tried to hook into the DNS resolver so that I could have it bypass my entry in the hostsfile. I couldn’t get that to work either. Thankfully the CNAME I found works so I was able to give up and call it done. None of that would have been necessary if I were willing to either run it in a docker container or somewhere in the cloud, but I was too lazy for either of those.


This was annoying, but way less annoying than setting up a Haskell environment to either backport a patch from a 2.x version or a 0.4.x version or compile and run the 2.x version myself. All told it took less than an hour.


(The following includes affiliate links.)

If you don’t already know Go, you should definitely check out The Go Programming Language. It’s not just a great Go book but a great programming book in general with a generous dollop of concurrency.

Another book to consider learning Go with is Go Programming Blueprints. It has a nearly interactive style where you write code, see it get syntax errors (or whatever,) fix it, and iterate. A useful book that shows that you don’t have to get all of your programs perfectly working on the first compile.

Posted Wed, Feb 27, 2019

If you're interested in being notified when new posts are published, you can subscribe here; you'll get an email once a week at the most.