SciTech.blog
SciTech.blog

Developing a web app with Go (1)

22 Sep 2017, 15:54 • go, web

This post is the first part of a miniseries on developing web apps with Go, a C-like programming language designed at Google.

Go is a modern programming languages which targets network and systems programming. It's both easy to learn and powerful and was designed to make developing concurrent applications simple and fun. This post illustrates how to develop a robust web server in Go.

You need only the standard library to get a robust and secure server up and running:

tc := &tls.Config{PreferServerCipherSuites: true, CurvePreferences: []tls.CurveID{tls.CurveP256, tls.X25519}}
app := &App{}
server := http.Server{
  Addr:           ":443",
  Handler:        app,
  ReadTimeout:    10 * time.Second,
  WriteTimeout:   10 * time.Second,
  IdleTimeout:    60 * time.Second,
  MaxHeaderBytes: 16 * 1024,
  TLSConfig:      tc,
}
done := make(chan struct{})
go func() {
  sig := make(chan os.Signal)
  signal.Notify(sig, os.Interrupt)
  <-sig
  server.Shutdown(context.Background())
  done <- struct{}{}
}()
err := server.ListenAndServeTLS("cert.pem", "key.pem")
if err != nil && err != http.ErrServerClosed {
  log.Fatal("can't listen on port 443: ", err)
}
if err != nil && err == http.ErrServerClosed {
  log.Print("server gracefully terminated")
}
<-done
app.TearDown()

The server will be securely listening on port 443. You'll need a signed certificate and a corresponding private key—it's probably best to use Let's Encrypt for it's free (and hasslefree). It's also a good idea to support a graceful shutdown of the server which the code does when it receives a SIGINT (for example, when the user uses the infamous Ctrl-C sequence). The Shutdown function serves all existing connections before it shuts down the server.

What's missing from the code snippet is the App type which is the core of the server as it handles incoming requests:

type App struct {
  sessions sync.Map
}

func (app *App) TearDown() {
  ...
}

func (app *App) ServeHTTP(w http.ResponseWriter, req *http.Request) {
  u, err := url.Parse(req.RequestURI)
  if err != nil {
    http.Error(w, "couldn't parse URI", http.StatusInternalServerError)
    return
  }
  switch req.Method {
    case http.MethodPost:
      switch u.EscapedPath() {
        case "/":
          w.Write([]byte("response"))
        default:
          http.Error(w, "unsupported request", http.StatusInternalServerError)
      }
    default:
      http.Error(w, "unsupported method", http.StatusInternalServerError)
  }
}

We can used a thread-safe map to manage sessions. It's also useful to parse the request URI because the server will need the path and possibly parameters. The response of the server depends on the method and the path. Note that unsupported methods and paths are reported as internal server errors.

Comments

Name: