Davide Muzzarelli

How to use Go and FastCGI

FastCGI is a good solution for using Go in a shared hosting like Dreamhost or also in a VPS. This post is about how to write a Go program in FCGI mode and deploy it on Apache.

In the CGI mode our program will be executed at every request. Go loads pretty fast so it may run fast like PHP, but it is possible to use a better approach.

FastCGI have the same protocol like CGI, but the web server loads the program one time then submit the requests. This is much more efficient, but the program have to be written better in order to keep a low use of RAM since the process will remain active for a while.

The web server can communicate with a FastCGI program in three manners: via the standard input/output, via a UNIX socket or via a TCP connection. In a shared hosting environment the standard input/output way is the most available and the web server will load the program for you automatically. Instead, using a UNIX or TCP socket, you will have to load and manage your program yourself monitoring it if it crashes (see Supervisord for example), because the web server can’t handle it for you.

The code

This example requires github.com/gorilla/mux and it’s tested on Golang 1.1.2, but it’s not mandatory:

go get github.com/gorilla/mux

The following is the source of the program (fcgitest.go):

import (
    "flag"
    "github.com/gorilla/mux"
    "io"
    "log"
    "net"
    "net/http"
    "net/http/fcgi"
    "runtime"
)

var local = flag.String("local", "", "serve as webserver, example: 0.0.0.0:8000")

func init() {
    runtime.GOMAXPROCS(runtime.NumCPU())
}

func homeView(w http.ResponseWriter, r *http.Request) {
    headers := w.Header()
    headers.Add("Content-Type", "text/html")
    io.WriteString(w, "<html><head></head><body><p>It works!</p></body></html>")
}

func main() {
    r := mux.NewRouter()

    r.HandleFunc("/", homeView)

    flag.Parse()
    var err error

    if *local != "" { // Run as a local web server
        err = http.ListenAndServe(*local, r)
    } else { // Run as FCGI via standard I/O
        err = fcgi.Serve(nil, r)
    }
    if err != nil {
        log.Fatal(err)
    }
}

The “flags” piks the command input. If “localhost” is set as a parameter, the program will run as a local web server and not in FCGI mode (see in your browser):

fcgitest -local=":8000"

The “init” function sets the number of processors to use at the same number of processors available by the machine.

The “homeView” function is a view that prints an HTML output to the user.

The main function sets up a URL router adding the “homeView” function at the root “/” path using the Gorilla handler.
The “if” part chooses if it runs as FCGI or as web server.

Pay attention at the “err = fcgi.Serve(nil, r)” line because it’s not documented well in the Go’s documentation: use “nil” for the standard input/output mode.

Run in TCP or Unix mode

Add two more flags for the TCP and UNIX socket modes:

var (
    local = flag.String("local", "", "serve as webserver, example: 0.0.0.0:8000")
    tcp   = flag.String("tcp", "", "serve as FCGI via TCP, example: 0.0.0.0:8000")
    unix  = flag.String("unix", "", "serve as FCGI via UNIX socket, example: /tmp/myprogram.sock")
)

…and change the “if” part like the following:

if *local != "" { // Run as a local web server
    err = http.ListenAndServe(*local, r)
} else if *tcp != "" { // Run as FCGI via TCP
    listener, err := net.Listen("tcp", *tcp)
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()

    err = fcgi.Serve(listener, r)
} else if *unix != "" { // Run as FCGI via UNIX socket
    listener, err := net.Listen("unix", *unix)
    if err != nil {
        log.Fatal(err)
    }
    defer listener.Close()

    err = fcgi.Serve(listener, r)
} else { // Run as FCGI via standard I/O
    err = fcgi.Serve(nil, r)
}
if err != nil {
    log.Fatal(err)
}

The same code can be written with a “switch” statement.

In this case we are creating a TCP or a UNIX listener so it have to be closed at the end of the execution.

Deployment under Apache

This is an example of deployment under Debian/Ubuntu using the standard I/O mode, it should be compatible with Dreamhost and is a fast way to test it.

First install mod_fcgid, enable it and reload Apache. For example, this is under Debian/Ununtu:

sudo apt-get install libapache2-mod-fcgid
sudo a2enmod fcgid
sudo service apache2 reload

Compile, rename the file and deploy it on the web server:

go build fcgitest
mv fcgitest fcgitest.fcgi

Change the file access permissions removing the write flag from “group” and “others” or Apache will raise a 118 error:

chmod go-w fcgitest.fcgi

Add the following “.htaccess” file, it will redirect all the requests that are not a file or a directory to the Go program through the FastCGI protocol via the standard I/O mode:

RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d [OR]
RewriteCond %{REQUEST_URI} ^/$
RewriteRule ^(.*)$ fcgitest.fcgi/$1 [QSA,L]

Done, try it visiting the web site.

Conclusions

Personally I like to use this protocol via standard I/O because I run several web sites on my servers, like a hosting company; this allows me to keep the things simple and with no effort with a low RAM usage.

Download the full Go source code.

Post to Hacker News

Comments

4 Risposte to “How to use Go and FastCGI”. Tutti gli utenti che hanno commentato hanno accettato le note legali.
  1. bsingr scrive:

    Thanks for this, I just put together https://github.com/bsingr/golang-apache-fastcgi to demo this.

  2. Davide Muzzarelli scrive:

    Thank you too Jens :)

  3. I come over and over again to this page of yours because I rarely remember the precise order of calling things to get FastCGI working under Go. Thank you so much!

  4. Baizid scrive:

    Great Post. The information you provided is really wonderful. Keep up the good work.

    Thanks
    DedicatedHosting4u.com

Dicci Cosa Pensi

Lascia un commento qui sotto...

Confermando l'invio accetti di aver letto le note legali e di aderire ad esse.