Hosting a Go Vanity Router with Cloudflare Pages
Make the 'go get' to your packages stand out
Introduction
Are you tired of having to expose your brilliant and professional Golang projects under your repository hosting service domain? Would you like to tell your coworkers to simply go get awesome.dev/amazing-project
(please don't try to run that, no idea where it might bring you...).
In this short guide, I'll show you how I set up a simple router using Cloudflare Pages (and Functions).
go get-ing stuff
Go doesn't offer any main registry like cargo, maven or npm. It operates slightly differently, and we're used to go get
or go install
the code from the repository directly. Therefore, any developer that hosts their projects on sites like GitHub or GitLab will have their modules imports be something like:
go get github.com/name-surname/package-name
Of course, this is a valid and hassle-free way of making your work available to the world, but it ends up a bit boring, doesn't it?
The 'need' for a vanity router
A vanity router, in this case, would be a simple redirecting service that can route the go get
request for a package to a custom URL. For example, with my router I translate all the requests coming to go.lorenzomilicia.dev/ to the corresponding GitHub repository under github.com/lorenzo-milicia/.
There is an actual real use case for the use of this rerouting mechanism, and it's the be independent of the git repository hosting service. You wouldn't want all of your users to have to change their import if you switched from GitHub to GitLab, right?
Behind the scenes
The <meta>
tag
Implementing the rerouting logic is extremely simple. Whenever a get
/install
request is made, you're appending to a query parameter go-get=1
. If the HTML that is served contains the meta tag
<meta name="go-import"
content="go.lorenzomilicia.dev/package git https://github.com/lorenzo-milicia/package">
then the server is telling the caller that the go.lorenzomilicia.dev/package is gettable, and specifies where it can be downloaded. Looking at the keys of the tag, we see the 'original' request and also the git repository to fetch the code.
The router
Building the router becomes easy! All that is needed is a server capable of returning us with an HTML page with the appropriate <head>
tag, based on the path the custom domain gets called with.
Hosting the server
To serve this small service, I decided to use Cloudflare Pages and its Functions, which are very similar to Cloudflare's Workers. I figured that serverless would fit this use case quite well: whenever someone calls the URL (in this case whenever someone go get
s one of my packages), the Function is executed, and the correct HTML gets served. This can be all done with a Cloudflare free subscription, as long as you mind the limitation on the number of requests that can be handled under this plan.
I'm not a Javascript/Typescript developer, so I'll spare you the simple code I wrote for my vanity router. I followed Cloudflare's documentation to build Functions in typescript.
After cloning my project, all you need is to use Clouflare's wrangler
CLI tool to publish your Cloudflare Page. Again, I'll point you to the exhaustive documentation.
How to use the vanity router
The logic behind the router relies on three external Environment Variables:
CUSTOM_DOMAIN
REPOSITORY_URL
CUSTOM_ROUTINGS
The first two are simply the routing you want to take effect, composed as this:
<meta name="go-import"
content="$[{CSTOM_DOMAIN}package git https://${REPOSITORY_URL}/package">
⚠ The protocol https:// is added in front of the repository URL passed as an environmental variable, so my implementation only works with the secure protocol!
The third variable should contain a comma-separated list of the custom mappings that the router should take into consideration when creating the repository URL.
For example, I want to keep all my library projects separate on GitHub, and I'll also most definitely write libraries with other languages. So I don't want to reserve a general libs prefix or directory name. But I would like the structure of my packages from the outside to keep a clean structure, so I want all my libraries to be gettable with the go.lorenzomilicia.dev/libs/. By settings CUSTOM_ROUTINGS="libs/,go-lib-"
, I'm telling the router that every request for a library /libs/
should go look for a GitHub repository that starts with go-lib
.
Let's make it clearer with a table:
Custom Routing? | Input | Output |
❌ | go.lorenzomilicia.dev/package | github.com/lorenzo-milicia/package |
✅ libs/ -> go-lib | go.lorenzomilicia.dev/libs/best-ever | github.com/lorenzo-milicia/go-lib-best-ever |
v2 and beyond
As you might know, Go has a peculiar way of handling major versions once you move to a 2.0 and above. Most notably, Go expects the module to be suffixed with a /v2
(or whatever major version the specific module is at). To correctly be able to route the requests to the repository that exposes a major version that requires this suffix, the path to the repository needs to be stripped from this.
The vanity router takes care of this, so the final outputs will be:
Major ⩾ 2? | Input | Output |
❌ | go.lorenzomilicia.dev/package | github.com/lorenzo-milicia/package |
✅ | go.lorenzomilicia.dev/package/v2 | github.com/lorenzo-milicia/package |
❌ | go.lorenzomilicia.dev/v4nity | github.com/lorenzo-milicia/v4nity |
Conclusions
Now you have all you need to host your personal Vanity Router for your Go projects, and all your friends and colleagues will be impressed by your professionalism, which under the hood will be mostly free of charge (excluding the domain you might want to route your requests from).
Check out the repository, and upload the files to your own Cloudflare Page to get started!
Links
https://github.com/lorenzo-milicia/go-vanity-router
https://developers.cloudflare.com/pages/get-started/ https://developers.cloudflare.com/workers/wrangler/commands/#pages https://developers.cloudflare.com/pages/platform/functions/