Apache web server and Go

This article is also available in German.

With the package net/http, Go offers a full-featured HTTP server implementation in the standard library. However, it can be useful to connect an application written in Go to an Apache web server. For example, serveral application written in different programming languages could be served by one Apache web server as virtual hosts or in different subdirectories. Apache also offers additional configuration options, e.g. user-defined logging, rewrite rules, and authentication.

A common interface between web servers and applications is FastCGI. Here, a socket (unix domain or TCP) is used as communication channel. The net/http/fcgi package from the Go standard library implements the server part of the FastCGI protocol.

Apache mod_fastcgi

For the Apache web server, FastCGI support is provided by the mod_fastcgi loadable module (Debian package libapache2-mod-fastcgi). It was written by Open Market Inc., the company which invented the FastCGI protocol. The module is provided under a somewhat restricted license and is therefore listed in the non-free section of Debian.

A simple FastCGI application in Go can look like this:

package main

import (
    "net/http"
    "net/http/fcgi"
    "runtime"
)

var reply = []byte("Hello, World!\n")

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write(reply)
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU()) // use all CPU cores

    err := fcgi.Serve(nil, http.HandlerFunc(handler))
    if err != nil { panic(err) }
}

In Debian, the Apache module is configured to treat executables with the .fcgi file name extension as FastCGI programs by default. Other applications can explicitly be named with the FastCgiServer directive. With the following lines within a VirtualHost configuration (e.g. on a Debian system in /etc/apache2/sites-available/default), the program /usr/local/sbin/fcgiprog is started when Apache starts, and handles all requests to an URI beginning with /fcgiprog.

        ...
        FastCgiServer /usr/local/sbin/fcgiprog
        RewriteRule ^/fcgiprog /usr/local/sbin/fcgiprog

Clean process termination

When the Apache HTTP server is restarted or its configuration is to be reloaded, the mod_fastcgi module sends a TERM signal to all running FastCGI applications. With the example program above, this leads to immediate termination of the program, and all running transactions are aborted.

For a clean termination of a FastCGI program, it must catch the SIGTERM signal and react to it in a proper way. Hereby, three things are to be considered:

  1. mod_fastcgi ensures that after sending SIGTERM, no further request is send to a running FastCGI program. However, for a short time, is is possible that pending requests are still being processed by net/http/fcgi where no request handler goroutine has been started yet. To catch these cases, the first thing a signal handler should do is wait for a short time (100ms in the example below).
  2. The program should not terminate until all open requests have been answered. This can be checked by calling runtime.NumGoroutine() and wait until the number of running goroutines has decreased to its initial value at program start (plus one for the FCGI handler).
  3. Depending on the program structure, it can be possible the additional goroutines have been started, which are not related to the handling of a specific request; for example, synchronization mechanisms in third-party libraries. To avoid lockup, the loop should terminated after a certain amount of time (3 seconds in the example).

When these issues are handeled properly, it is possible to redeploy a Go application simply by exchanging the binary and issuing an Apache graceful reload. Even under load, no client connection is lost.

// Example for a FastCGI program that terminates gracefully.
package main

import (
    "net/http"
    "net/http/fcgi"
    "os"
    "os/signal"
    "runtime"
    "syscall"
    "time"
)

// a simple request handler
var hello = []byte("Hello, World!\n")

func handler(w http.ResponseWriter, r *http.Request) {
    w.Write(hello)
}

func main() {
    runtime.GOMAXPROCS(runtime.NumCPU())     // use all CPU cores
    n := runtime.NumGoroutine() + 1          // initial number of Goroutines

    // install signal handler
    c := make(chan os.Signal, 1)
    signal.Notify(c, syscall.SIGTERM)

    // Spawn request handler
    go func() {
        err := fcgi.Serve(nil, http.HandlerFunc(handler))
        if err != nil {
            panic(err)
        }
    }()

    // catch signal
    _ = <-c

    // give pending requests in fcgi.Serve() some time to enter the request handler
    time.Sleep(time.Millisecond * 100)

    // wait at most 3 seconds for request handlers to finish
    for i := 0; i < 30; i++ {
        if runtime.NumGoroutine() <= n {
            return
        }
        time.Sleep(time.Millisecond * 100)
    }
}

Other FastCGI implementations

There is another Apache module, mod_fcgid. It is sometimes recommended as an alternative to mod_fastcgi, because it is Free Software and is supposed to offer better control over process creation. However, it misses the ability to send several concurrent requests to the same FastCGI process. Concurrent requests can only be handled if several independend processes of the FastCGI program are started. This means that an important property of Go’s FastCGI implementation cannot be used: the ability to handle concurrent requests in goroutines. mod_fcgid should therefore not be considered in combination with Go.