11月02, 2013

[译]go如何应用fastcgi

原文连接:http://www.dav-muz.net/blog/2013/09/how-to-use-go-and-fastcgi/

在一个类似Dreamhost共享主机或在VPS使用Go,FastCGI是一个很好的解决方案。这篇文章是关于如何写一个使用FCGI模式的Go程序,并将其部署在Apache 。

在CGI模式下,每个请求会执行我们的程序。由于Go加载相当快,所以它可能跟PHP跑得快 ,但还有一个更好的方法可以使用。

FastCGI有跟CGI相同的协议,不同的是Web服务器只加载一次程序,然后提交请求转发。这是更有效率的,但是有点要注意,程序必须设计得好,以维持较低的RAM占用 ,因为进程会保持一段时间。

Web服务器与FastCGI程序沟通有三种方式:通过标准输入/输出,UNIX套接字或TCP连接。在共享主机环境标准输入/输出的方法是最合适的方式而且Web服务器将自动为您加载程序。相反,使用UNIX或TCP套接字,你需要手动加载和管理你的程序还有监控它,以防它万一崩溃(见例如Supervisord ) ,因为web服务器无法为您处理。

代码

这个例子需要github.com/gorilla/mux,在Golang 1.1.2 已测试过,但是并不强制要求这个。

go get github.com/gorilla/mux

下面是源码:(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)
    }
}

“flags”解析命令输入。如果“localhost”设置为参数,该程序将作为本地Web服务器上运行,而不是在FCGI模式(浏览器测试):

fcgitest -local=":8000"

“init”函数设置runtime使用与实机相同数量的处理器数目。

“homeView”函数是一个打印输出HTML给用户看的视图。

“main”给URL路由器添加了“homeView”函数映射,在“/”根路径通过Gorilla处理。

“if”部分是选择它运行时用FCGI还是作为Web服务器。 注意err= fcgi.Serve(nil,r)行,因为它在Go的文档记录比较模糊:使用“nil”为标准的输入/输出模式。

运行 TCP 或 Unix模式

添加两个选项,tcp与unix sock 模式:

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")
)

…更换if部分的block源码如下:

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)
}

同样也可用switch 表达式。

在这种情况下,我们创建了一个TCP或UNIX监听器,所以它在执行结束时务必要关闭。

部署Apache

这是基于Debian / Ubuntu系统下部署,使用标准的I / O模式的一个例子,它应该是兼容Dreamhost虚拟主机而且是一个便于快速测试的方法。

首先安装mod_fcgid,启用它,并让Apache重新载入的。例如,这是使用Debian/ Ununtu下:

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

编译,重命名文件,并将其部署在Web服务器上:

go build fcgitest
mv  fcgitest fcgitest.fcgi

移除从“group”和“others”的写标志或Apache以更改文件的访问权限,否则将引发代号为118的错误:

chmod go-w fcgitest.fcgi

添加以下.htaccess文件,它会重定向所有的请求,不管是一个文件或目录都会通过遵守FastCGI协议应用标准I / O模式的Go程序:

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

完成后尝试访问这个网站。

结论

我个人喜欢使用通过标准I / O协议,因为我在我的服务器上运行着多个网站,就像一个托管公司,这让我能够保持做着简单的事情,不受内存占用低的影响。

文中代码连接 Download