lighttpd 集成 golang

lighttpd 集成 golang

Author:  nullchen
Email:   526624974@qq.com

简介:

我们业务用fastcgi做接入,抽空研究了下fcgi如何运行在httpserver之上,主要是fcgi与httpserver的通信,在这里简单的记录一下。由于qzhttp是非开源的,这里以lighttpd位对象,进行学习。本文分两部分,第一部分简单的分析lighttpd如何与fastcgi应用通信,在此基础上,第二部分位了对第一部分的结论进行验证,简单的将golang用fastcgi的方式集成到lighttpd中,让lighttpd管理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程序如下:

#!/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模块,为了阅读方便进行了大量精简):

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

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.

可见,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",
                 )
    ),
)

本文来自:开源中国博客

感谢作者:dadada110

查看原文:lighttpd 集成 golang

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。