You should write your next web server in Go. Yes, you! Compared with Ruby, PHP, Python, or Javascript, you're going to get great memory and latency performance, and libraries that do what you expect.
The standard library can be a bit lacking though, if you are used to developing with a tool like Rails. I found myself adding the same helpers to every web project that I started in Go, so I compiled those tools into a starter pack. Most of the code is thin wrappers around the standard library, and any/all of it can be ripped out. I wanted to go over some of the tools in the starter pack.
Serving Static Assets
I compile static assets into the binary. This way you don't need to worry about copying static assets to your deployment server, passing in the relative path from the working directory to the static asset directory, or ensuring that you have the right version of the static assets for the right version of the binary.
A make target runs go-bindata
, which compiles everything in the
static directory and the templates directory into a single Go file. When a
request comes in for static assets, we find the right one and serve it:
data, err := assets.Asset(strings.TrimPrefix(r.URL.Path, "/")) if err != nil { rest.NotFound(w, r) return } http.ServeContent(w, r, r.URL.Path, s.modTime, bytes.NewReader(data))
Tests ensure that the static files on disk and the Go assets are up to date.
Routing
The Go http router only offers exact match, or the ability to route a directory,
e.g. http.Handle("/static/", staticHandler())
. I use a http.Handler that
takes a regular expression as the first argument, instead of a string,
and lets you specify which methods you want to handle.
r := new(handlers.Regexp) // github.com/kevinburke/handlers r.HandleFunc(regexp.MustCompile("^/$"), []string{"GET"}, func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") io.WriteString(w, "<html><body><h1>Hello World</h1></body></html>") })
You can use regexp.FindStringSubmatch to pull parameters out of the URL, or replace this with your own routing framework.
HTTP/2 Server Push
"We shall go south," Paul said.
"Even if I say we shall turn back to the north when this day is over?" Stilgar said. "We shall go south," Paul repeated.
r.HandleFunc(regexp.MustCompile(`^/$`), []string{"GET"}, func(w http.ResponseWriter, r *http.Request) { if pusher, ok := w.(http.Pusher); ok { pusher.Push("/static/style.css", nil) } // Render the homepage HTML })
It's really, really easy to do server push in Go, and the idea of pushing resources your clients are about to ask for is so exciting that I wanted to use it even if the benefits aren't going to be that large for your project.
Server push requires that you terminate TLS in your Go server. Run
make generate_cert
to generate a self-signed certificate for use locally. Use
autocert.NewListener()
for one-line certificate provisioning in
production.
If you can't terminate TLS (say you are running on App Engine Flex or Heroku), the starter pack falls back to sending a preload Link header.
Logging
Being able to see details about every incoming request and response helps with debugging and is rarely a performance bottleneck on a domain that's being browsed by humans. In particular it's useful to see the user agent, the status code, the request ID (for correlating with downstream services), and any username used for HTTP basic authentication.
INFO[07:52:24.810-07:00] method=GET path=/ time=0 bytes=468 status=200 remote_addr=[::1]:50170 host=localhost:7065 user_agent=HTTPie/1.0.0-dev request_id=a8cf0a8c-4f30-4b01-a49d-396611b3ca15
I use a logging middleware for this but you are welcome to rip it out and use your own.
Flash Messages
You'll probably want to show error and success messages to your end users. Again this is trying to take the simplest possible approach - a flash message is set by encrypting a string and setting the result as a cookie. Retrieving the message deletes the cookie.
func FlashSuccess(w http.ResponseWriter, msg string, key *[32]byte) { c := &http.Cookie{ Name: "flash-success", Path: "/", Value: opaque(msg, key), HttpOnly: true, } http.SetCookie(w, c) }
We use the secretbox
library to encrypt and decrypt cookies using
a secret key - you can use the same to encrypt session ID's or other data in
a tamper-safe way.
Loading Configuration
You will probably need to configure the server somehow. I prefer loading
configuration from a YAML file to the command line or to environment variables,
because it's more difficult to accidentally leak secrets from YAML to e.g.
Sentry (as you can with environment variables), and your secrets can't be read
from /proc/<pid>/env
by other users.
main.go
walks you through loading a YAML file and using it to configure the
server.
Release Binaries
Run make release version=0.x.y
to increment the server's Version, cross
compile binaries for Windows, Mac, and Linux, and push them to the releases
page on Github. You'll want to set a GITHUB_TOKEN
in the
environment with permission to push new releases to your project, and you'll
probably need to change the username in your version of the Makefile.
Conclusion
It's not too difficult to get a lot of the core functionality of a larger framework in a different language, with a lot better performance and a more understandable set of libraries. It should be pretty easy to rip any of these parts out; you should deploy this by copying the files into your own project and modifying as you see fit.
I hope the starter pack is useful and I hope you choose Go for your next web development project!
Liked what you read? I am available for hire.
Thanks for your hardwork.
I’m having problem getting the Hello World to show up. I’m getting the following errors from the console.
2017/04/10 17:36:05 http: TLS handshake error from [::1]:49934: tls: first record does not look like a TLS handshake
What should I do?
Use HTTPS – https://localhost:7065 instead of HTTP
Excellent. I tried to use the boiler plate with Chrome and it wouldn’t use the self signed certificate. I had to install Firefox-esr to allow the browser to use the self signed certificate.
Thanks again! I plan to use this.