lighttpd 集成 golang

#lighttpd 集成 golangpython

Author:  nullchen
Email:   526624974@qq.com

##簡介: 咱們業務用fastcgi作接入,抽空研究了下fcgi如何運行在httpserver之上,主要是fcgi與httpserver的通訊,在這裏簡單的記錄一下。因爲qzhttp是非開源的,這裏以lighttpd位對象,進行學習。本文分兩部分,第一部分簡單的分析lighttpd如何與fastcgi應用通訊,在此基礎上,第二部分位了對第一部分的結論進行驗證,簡單的將golang用fastcgi的方式集成到lighttpd中,讓lighttpd管理golang進程,從而實現 多進程+多線程+多協程 的模式。golang

##第一部分數據結構

爲了描述清楚,咱們首先按照功能進行角色劃分以下:多線程

fastcgi client:
    fastcgi client的功能主要是將收到的http請求轉換成fastcgi請求發送給fastcgiserver,收到應答後轉換成http格式的應答併發送給http請求者。lighttpd具備fastcgi client的功能。

fastcgi server:
    fastcgi server的主要功能主要是進行進程的管理。並在各個進程中執行fastcgi application。從而使fastcgi application專一於業務邏輯。lighttpd具備fastcgi server的功能。

fastcgi application:
    該角色的主要功能是接受fastcgi server(lighttpd)發送過來的請求,按照fastcgi協議進行解碼(通常由庫提供,好比說咱們寫fcgi是使用的庫),以及作業務邏輯的處理(業務邏輯處理部分通常指咱們平時寫的fcgi代碼部分).

咱們以lighttpd 的fastcgi模塊和pyhton的flup模塊爲基礎來探索下python寫的fastcgi程序是如何運行在lighttpd中的。併發

咱們先來看一個python實現的hello world fastcgi程序以下:app

#!/usr/bin/python
from flup.server.fcgi import WSGIServer

def app(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Hello World!\n']

if __name__ == '__main__':
    WSGIServer(app).run()

將上述代碼保存爲hello_world.fcgi 放在lighttpd中便可運行。顯然,與lighttpd的通訊是在flup.server.fcgi.WSGIServer這個模塊中實現的,那麼咱們進一步的跟進該模塊,發現關鍵代碼以下(注:以下代碼摘抄自flup模塊,爲了閱讀方便進行了大量精簡):socket

FCGI_LISTENSOCK_FILENO = 0  --> 標準輸入
sock = socket.fromfd(FCGI_LISTENSOCK_FILENO, socket.AF_INET,
                                    socket.SOCK_STREAM)
while self._keepGoing:
    r, w, e = select.select([sock], [], [], timeout)
    if r:
        clientSock, addr = sock.accept()
        # 通過上步驟就創建起來了一個鏈接,而後在該鏈接上對讀事件進行處理並按照fastcgi協議進行解碼後獲得請求,而後用業務邏輯對該請求進行處理後獲得應答,並將應答

其中_socket.fromfd_文檔以下(摘自python 標準庫手冊)學習

socket.fromfd(fd, family, type[, proto])ui

Duplicate the file descriptor fd (an integer as returned by a file object’s fileno() method) and build a socket object from the result Address family, socket type and protocol number are as for the socket() function above. The file descriptor should refer to a socket, but this is not checked — subsequent operations on the object may fail if the file descriptor is invalid. This function is rarely needed, but can be used to get or set socket options on a socket passed to a program as standard input or output (such as a server started by the Unix inet daemon). The socket is assumed to be in blocking mode.this

可見,fastcgi application 是對標準輸入(fd=0)進行accept操做創建鏈接,而後進行讀寫的。然而,咱們知道 標準輸入 是不支持accept操做的,所以能夠猜想lighttpd在啓動 fastcgi application 的時候會把_fastcgi application_的標準輸入關聯到一個支持 accept操做的數據結構上。順着這個思路咱們來把一把lighttpd的實現。在lighttpd的Mod_fastcgi.c中 fcgi_spawn_connection 找到以下代碼:

FCGI_LISTENSOCK_FILENO=0
fcgi_fd = socket(socket_type, SOCK_STREAM, 0))
setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val))
bind(fcgi_fd, fcgi_addr, servlen)
listen(fcgi_fd, 1024)

switch ((child = fork())) {
    case 0: {    
        dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO)

        close(fcgi_fd)
        ......
        execve(arg.ptr[0], arg.ptr, env.ptr);    ---->這裏執行_fastcgi application_ 程序。
    }
}

其中 **dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO) **

這行代碼是關鍵,將子進程的 標準輸入 指向到了一個處於 listen 狀態的unixsocket fd。所以在 fastcgi application 中才可能對 標準輸入 進行accept操做。

結論:由此咱們能夠獲得這樣一個結論。fastcgi server(lighttpd)與fastcgi application 數據交互步驟以下:

*  fastcgi server bind,listen 一個unix socket 獲得一個fd
*  fastcgi server fork
*  子進程 dup2(fd,stdin) -->將子進程的stdin指向一個處於listen狀態的unix socket
*  子進程中執行 fastcgi application
*  fcgi app中 accept stdin 等待鏈接到來並處理
*  fcgi server connect 對應的unixsocket併發收數據

##第二部分

根據第一部分的結論。由此獲得以下的golang代碼,該代碼能夠以fastcgi的方式運行在lighttpd中。

package main
import (
    "net"
    "net/http"
    "net/http/fcgi"
    "os"
    "time"
)

type FastCGIApp struct{}

func (s *FastCGIApp) ServeHTTP(resp http.ResponseWriter, req *http.Request) {

    msg := req.FormValue("msg")
    if msg == "sleep" {
        time.Sleep(time.Second * 60)
    }
    resp.Write([]byte(msg))
}

func main() {

    listener, _ := net.FileListener(os.Stdin)
    app := new(FastCGIApp)
    fcgi.Serve(listener, app)

}

對上述代碼的處理:

0. 保存爲 try_fastcgi.go
1. go build try_fastcgi.go 獲得可執行文件 try_fastcgi
2. mv try_fastcgi test.fcgi
3. 將test.fcgi 放到lighttpd 對應的位置便可

lighttpd 配置以下:

server.modules += ( "mod_fastcgi" )
fastcgi.server = (
    ".fcgi" =>
    ( "fcgi-num-procs" =>
                 (
                   "socket" => socket_dir + "/fastcgi-test.socket",
                   "bin-path" => server_root + "/cgi-bin/test.fcgi",
                   "max-procs" => 1,
                   "min-procs" => 1,
                   "broken-scriptfilename" => "enable",
                 )
    ),
)
相關文章
相關標籤/搜索