Embedding Caddy Web Server in Go

Web servers help us to manage and scale our web apps in production. They have been the mysterious part of the stack to some web developers. It’s because configuring a web server is fairly time consuming for first timers as they need to know how reverse-proxies work and also have to deal with quirky config files.

But thanks to Caddy. It removes complexity surrounding the webservers. Developers who have used Caddy know the drill. Download a binary file, write few lines of config, start the binary by pointing it to the config file. Now you will have a secure web server running as caddy takes care of SSL by itself.

We know that CaddyServer is simple. But do you know that it’s extensible too? Yes, you can import caddy as library and embed the web server inside your Go code.

import "github.com/mholt/caddy"

When the web server is embedded into the code, we get more control over it. Adding a battle tested web server into your code allows you to use your code as reverse-proxy with graceful restarts or even for load balancing.

Today you will learn how to use caddy server as library in Go code. We will build a simple web app that runs on port 8000 and route all the requests hitting port 80 to it using caddy server as library.

High level representation of what we are going to build

A basic Caddyfile looks like this when you reverse proxy a web service running on port 8000 to the port 80.

:80
proxy / localhost:8000

Now let’s start building our components one by one.

Simple HTTP server

We will write a dead simple HTTP server using mux router that exposes an endpoint /ping.

A simple http server using mux router

The code is fairly simple here, we use gorilla/mux as router and send a plain text response to the /ping endpoint by starting http server on port 8000.

Visiting the URL localhost:8000/ping in browser after running the above code should output this.

localhost:8000/ping

Now we have got our HTTP server up and running. Let us first proxy it through port 80 using standalone caddy server manually. The Caddyfile will look like this.

:80
proxy / localhost:8000

Start the Caddy server by pointing it to the Caddyfile.

./caddy --conf Caddyfile

If both HTTP server and this caddy server is running, opening localhost:80/ping in the browser will route requests to localhost:8000/ping and give you the text response.

Embedding a basic caddy server

We will replace the standalone caddy server in above step with an embedded server. In order to do that, first we need to install caddy as a library by doing go get github.com/mholt/caddy.

Here’s the code to embed a basic version of caddy server.

Basic example as seen in official wiki page.

Inside main() we first set the AppName and AppVersion for our caddy instance.

Then we simply load a default config with server type http by doing caddy.LoadCaddyfile("http") and start the caddy server. This does not load any config file from disk, instead it simply starts a server on port 2015.

⚠️ We are setting up server type as http, so we have to make sure server type package_ "github.com/mholt/caddy/caddyhttp" is imported, else code will panic.

Running the above code spits this output.

Activating privacy features... done.
http://:2015
2018/12/08 14:20:21 http://:2015

Caddy says the server has been started on port 2015. If you goto localhost:2015 you should get a 404 error from caddy server as there is no upstream connected to that port.

404 error returned from our caddy server

Now we have confirmed that the caddy server is running as library. It’s time to make use of it by reverse-proxying an upstream server, the simple HTTP server we initially built.

Reading caddy config file

We can make the previous snippet to read the config from Caddyfile by adding couple of functions to it. loadConfig() function to read the contents of the caddy config file and init() to tell caddy which function to use inorder to load the config file.

The loadConfig() function assembles the CaddyfileInput struct by accepting contents of the Caddyfile, it’s Filepath and serverType. The ServerTypeName and Contents are important fields of the struct where as Filepath is just for logging or reference purpose.

Inside init() you tell the caddy server to use the function loaderConfig() to prepare the CaddyfileInput. When you run this snippet, it proxies the http port: port 80 to the port 8000. But before doing that we need to start our upstream HTTP server by running the snippet simpleHTTPServer.go.

Now all your requests to localhost:80 are proxied to the upstream server running on localhost:8000. Hitting /ping endpoint returns the response served from the upstream server.

‘/ping’ API served on port 80 from upstream server

Playing around with the code

The snippets have been wrapped into its own packages in this repository.

Clone the repository and cd to embed_caddy folder. The code structure there should look like this:

embed_caddy
|
|_ main.go
|
|_ glide.lock
|
|_ glide.yaml
|
|_ simpleserver/
| |
| |_ httpserver.go
|
|_ webserver/
|
|_ server.go

I have used Glide as a package manager here. So, install glide by doing brew install glide. If you’re not on macOS, do 
go get github.com/Masterminds/glide.

Install the dependencies by running glide install from inside the embed_caddy folder. Now you are ready to build and run the code.


Found this post interesting?
It would mean a lot to me if you could hold the “clap” icon and give a shoutout to me on
twitter. That would really make my day.

Also you can subscribe to our newsletter to receive more blog posts and courses on backend engineering.
 — thanks!