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.
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.
A basic Caddyfile looks like this when you reverse proxy a web service running on
port 8000 to the
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
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
Visiting the URL
localhost:8000/ping in browser after running the above code should output this.
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.
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.
main() we first set the
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
⚠️ 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.
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.
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.
loadConfig() function assembles the
CaddyfileInput struct by accepting
contents of the Caddyfile, it’s
Contents are important fields of the struct where as
Filepath is just for logging or reference purpose.
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
Now all your requests to
localhost:80 are proxied to the upstream server running on
/ping endpoint returns the response served from the upstream server.
Playing around with the code
The snippets have been wrapped into its own packages in this repository.
Clone the repository and
embed_caddy folder. The code structure there should look like this:
| |_ httpserver.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.