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