CGI -> Apache Mod_* -> FastCGI -> (Servlet、WSGI等)
CGI是一个非常古老的协议,因为性能问题,现在基本没人使用CGI了。同时,出于安全性考虑,Nginx并不支持运行外部程序,所以Nignx原生是不支持CGI的,为了测试,我们先安装支持CGI的Apache(本文是在CentOS7下测试的):
sudo yum install -y httpd httpd-devel httpd-tools
Apache的默认配置文件在:/etc/httpd/conf/httpd.conf。下面看一些主要配置:
Listen 80;
、Listen 127.0.0.1:80;
。LoadModule foo_module modules/mod_foo.so
指令加载它。静态编译的模块(可以通过httpd -l列出),无需在此指定。
主服务器 配置:
接下来的这些指令用来设置主服务器所使用的配置,主服务器负责处理所有没有被任何虚拟主机处理的请求。同时,这些配置也负责为虚拟主机的配置提供默认值。
这些指令也可以出现在<VirtualHost>标签中,此时,它们会覆盖从主服务器继承来的默认值。
<Directory Directory-path>...</Directory>
。Options [+|-]option1 [+|-]option2 ...
。Alias /images/poweredby.png /usr/share/httpd/noindex/images/poweredby.png
Alias /webpath/ /full/filesystem/path/
ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
# Customizable error responses come in three flavors: # 1) plain text 2) local redirects 3) external redirects # # Some examples: #ErrorDocument 500 "The server made a boo boo." #ErrorDocument 404 /missing.html #ErrorDocument 404 "/cgi-bin/missing_handler.pl" #ErrorDocument 402 http://www.example.com/subscription_info.html
<Location URI>...</Location>
。下面我们先看一个CGI的例子:
# /etc/httpd/conf/httpd.conf ServerRoot "/etc/httpd" Listen 8081 User apache Group apache ServerAdmin jordan23nbastar@yeah.net ServerName 47.52.166.98:8081 Include conf.modules.d/*.conf <IfModule alias_module> ScriptAlias /cgi-bin/ "/var/www/cgi-bin/" </IfModule> ErrorLog "logs/error_log" LogLevel warn <IfModule log_config_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined CustomLog "logs/access_log" combined </IfModule> <IfModule mime_magic_module> MIMEMagicFile conf/magic </IfModule> <IfModule mime_module> TypesConfig /etc/mime.types AddType application/x-compress .Z AddType application/x-gzip .gz .tgz AddType text/html .shtml AddOutputFilter INCLUDES .shtml </IfModule>
#! /usr/bin/env python import os import sys import json print "Content-Type: application/json" print d = {} d["pid"] = os.getpid() d["command_line_args"] = sys.argv[1:] d["stdin"] = sys.stdin.read() d["environment"] = dict([(str(k), str(v)) for k, v in os.environ.iteritems()]) print json.dumps(d, indent=2)
chmod a+x /var/www/cgi-bin/test.py
sudo systemctl restart httpd.service
[root@iZj6chejzrsqpclb7miryaZ nginx]# curl http://timd.cn:8081/cgi-bin/test.py?is_index { "environment": { "CONTEXT_DOCUMENT_ROOT": "/var/www/cgi-bin/", "SERVER_SOFTWARE": "Apache/2.4.6 (CentOS)", "CONTEXT_PREFIX": "/cgi-bin/", "SERVER_SIGNATURE": "", "REQUEST_METHOD": "GET", "SERVER_PROTOCOL": "HTTP/1.1", "QUERY_STRING": "is_index", "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", "HTTP_USER_AGENT": "curl/7.29.0", "SERVER_NAME": "timd.cn", "REMOTE_PORT": "47284", "SERVER_PORT": "8081", "SERVER_ADDR": "172.31.238.196", "DOCUMENT_ROOT": "/etc/httpd/htdocs", "SCRIPT_FILENAME": "/var/www/cgi-bin/test.py", "SERVER_ADMIN": "jordan23nbastar@yeah.net", "HTTP_HOST": "timd.cn:8081", "SCRIPT_NAME": "/cgi-bin/test.py", "REQUEST_URI": "/cgi-bin/test.py?is_index", "HTTP_ACCEPT": "*/*", "GATEWAY_INTERFACE": "CGI/1.1", "REMOTE_ADDR": "47.52.166.98", "REQUEST_SCHEME": "http", "UNIQUE_ID": "Wk7v0hrJnREyoIubeYkhzAAAAAA" }, "stdin": "", "pid": 21243, "command_line_args": [ "is_index" ] } [root@iZj6chejzrsqpclb7miryaZ nginx]# curl http://timd.cn:8081/cgi-bin/test.py?is_index { "environment": { "CONTEXT_DOCUMENT_ROOT": "/var/www/cgi-bin/", "SERVER_SOFTWARE": "Apache/2.4.6 (CentOS)", "CONTEXT_PREFIX": "/cgi-bin/", "SERVER_SIGNATURE": "", "REQUEST_METHOD": "GET", "SERVER_PROTOCOL": "HTTP/1.1", "QUERY_STRING": "is_index", "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", "HTTP_USER_AGENT": "curl/7.29.0", "SERVER_NAME": "timd.cn", "REMOTE_PORT": "47286", "SERVER_PORT": "8081", "SERVER_ADDR": "172.31.238.196", "DOCUMENT_ROOT": "/etc/httpd/htdocs", "SCRIPT_FILENAME": "/var/www/cgi-bin/test.py", "SERVER_ADMIN": "jordan23nbastar@yeah.net", "HTTP_HOST": "timd.cn:8081", "SCRIPT_NAME": "/cgi-bin/test.py", "REQUEST_URI": "/cgi-bin/test.py?is_index", "HTTP_ACCEPT": "*/*", "GATEWAY_INTERFACE": "CGI/1.1", "REMOTE_ADDR": "47.52.166.98", "REQUEST_SCHEME": "http", "UNIQUE_ID": "Wk7v2NJfx11vvtodsSxwwwAAAAU" }, "stdin": "", "pid": 21246, "command_line_args": [ "is_index" ] }
我们可以看到两次的pid是不一样的。
下面简单介绍一下CGI的整个执行流程:
综上,可以看出,CGI的执行模式是:fork-execute-destroy,这正是其性能低下的原因。
当使用Python开发CGI程序的时候,可以使用wsgiref模块。
Apache mod_*模块的本质就是将各种脚本语言(比如PHP、Perl、Python等)的解释器内嵌到Apache中,这样会获得两方面的好处:
但是,随之也引来了坏处:
非常值得说明的是:Openresty也是将Lua解释器或LuaJIT解释器以模块的方式嵌入到Nginx中,进而使得Nginx有运行Lua脚本的能力。并且,Nginx官方最近推出了nginScript,其本质是将(他们自研的)JS解释器嵌入到Nginx,它通过融入JavaScript代码,对NGINX的配置语法进行扩展,以便实现复杂的配置。
接下来,我们以Apache的mod_wsgi为例,进行一下实践。mod_wsgi模块可以支持任何实现WSGI标准的Python应用程序。
1,安装mod_wsgi
可以参考PYPI的说明。
鄙人是通过pip install mod_wsgi
安装的,然后将/path/to/site-packages/mod_wsgi/server/mod_wsgi-py27.so
拷贝到/etc/httpd/modules
下。
2,编写WSGI应用程序
# /etc/httpd/wsgi.py import os import threading counter = 0 def application(environment, start_response): global counter counter = counter + 1 response = "pid is: %d, thread is: %d, counter is: %d" % ( os.getpid(), threading.currentThread().ident, counter) length = len(response) writer = start_response("200 OK", [("Content-Length", str(length))]) return [response]
3,配置Apache
本例中使用的mpm模式是worker模式:
# /etc/httpd/conf/httpd.conf ServerRoot "/etc/httpd" Listen 8081 User apache Group apache ServerAdmin jordan23nbastar@yeah.net ServerName 47.52.166.98:8081 Include conf.modules.d/*.conf LoadModule wsgi_module modules/mod_wsgi.so <IfModule wsgi_module> WSGIScriptAlias /wsgi /etc/httpd/wsgi.py WSGIPythonPath /etc/httpd/ </IfModule> <Files /etc/httpd/wsgi.py> Require all granted </Files> <IfModule mpm_worker_module> ServerLimit 1 StartServers 1 MaxSpareThreads 20 MinSpareThreads 15 ThreadsPerChild 25 </IfModule> ErrorLog "logs/error_log" LogLevel info <IfModule log_config_module> LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined CustomLog "logs/access_log" combined </IfModule> <IfModule mime_magic_module> MIMEMagicFile conf/magic </IfModule> <IfModule mime_module> TypesConfig /etc/mime.types AddType application/x-compress .Z AddType application/x-gzip .gz .tgz AddType text/html .shtml AddOutputFilter INCLUDES .shtml </IfModule>
4,重启Apache,并测试一下
[root@iZj6chejzrsqpclb7miryaZ httpd]# systemctl restart httpd [root@iZj6chejzrsqpclb7miryaZ httpd]# curl http://127.0.0.1:8081/wsgi pid is: 14938, thread is: 140189502514944, counter is: 1
上面的例子中,使用的是mod_wsgi的嵌入模式,用到的指令包括:
1,WSGIScriptAlias
URL-path file-path|directory-path [ options ]
与Alias指令类似,都是将URL映射到本地文件系统的特定区域。当第二个参数是目录的时候,那么会把它当作包含WSGI脚本的目录;当第二个参数是文件的时候,那么会把它当作WSGI脚本(它会被mod_wsgi的wsgi-script
handler处理)。
比如:
WSGIScriptAlias /wsgi-scripts/ /web/wsgi-scripts/
那么,请求http://www.example.com/wsgi-scripts/name
会导致 服务器运行 /web/wsgi-scripts/name
应用程序。
再比如:
WSGIScriptAlias /name /web/wsgi-scripts/name
那么,请求http://www.example.com/name
会导致 服务器运行 /web/wsgi-scripts/name
应用程序。
2,WSGIPythonPath
directory|directory-1:directory-2:…
用于添加Python的模块搜索路径,多个路径之间用:
分隔(windows上用;
分隔)。
除了嵌入模式,mod_wsgi还支持后台模式(非常值得动手实践一下)。更多关于mod_wsgi的配置和运行模式的资料,请参考:
参考文档8
参考文档9
PHP目前最常用的部署方式就是Nginx + PHP-FPM。FPM的全称是FastCGI Process Manager。故名思意,PHP-FPM就是PHP-CGI进程的管理器。
前面已经提及,CGI性能低的原因是:每次请求都要Fork-Execute-Destroy。而FastCGI进程是常驻(long-live)的,一旦启动,它会一直存在,不必每次请求都fork一次。
一般情况下,FastCGI的完整执行流程是:
值得额外说明的是:在Web服务器的配置中,需要正确的设置FastCGI参数 SCRIPT_FILENAME
,否则会导致“Primary script unknown”之类的错误,该代表CGI脚本的完整路径。
接下来看一个Nginx + spawn-fcgi + Python flup的例子:
server { listen 8081; server_name test.timd.cn; location / { fastcgi_pass 127.0.0.1:9091; include fastcgi_params; } }
#! /usr/bin/env python from flup.server.fcgi import WSGIServer def application(enviroment, start_response): response = "just test for fastcgi" length = len(response) start_response("200 OK", [ ("Content-Length", str(length)) ]) return [response] if __name__ == "__main__": WSGIServer(application).run()
chmod a+x fastcgi.py spawn-fcgi -f ./fastcgi.py -a 0.0.0.0 -p 9091 -F 4 -n
-F
:用来指定子进程数
-f
:用来指定fastcgi应用程序的文件名(文件必须是可执行的)
-a
:用来指定绑定的IP地址
-p
:用来指定要监听的端口
-n
:表示前台运行
[root@iZj6chejzrsqpclb7miryaZ ~]# curl http://127.0.0.1:8081 just test for fastcgi
spawn-fcgi是从lighttpd脱离出来的,其一个不能容忍的缺点是:当它fork出的FastCGI进程被杀死后,它不会重启一个新的。
PEP 3333详尽的描述了WSGI标准。感兴趣的童鞋,可以点击链接阅读英文原稿。不爱阅读英文文档的盆友,也可以看下这篇比较不错的中文文档。
=====下面开始我的表演=====
WSGI的全称是Web Server Gateway Interface。它是一个描述:
的标准。
因为HTTP只能处理字节,所以WSGI应用程序返回的响应必须是字节,不能是unicode。在Python中,字节是指str(In Python2X)或bytes(In Python3X)。
WSGI包含三面的标准:
1,WSGI应用程序
sys.exc_info()
write(body_data)
传递数据(不建议,只是为了向前兼容)write(body_data)
或第一次返回数据数据之前,调用start_response
。这是因为前两者对应的是body数据,而start_response
对应的是响应行和响应头
2,WSGI服务器
3,WSGI中间件
对于WSGI服务器来说,它相当于WSGI应用程序;对于WSGI应用程序来说,它相当于WSGI服务器。
4,注意事项
Content-Length
头,那么多返回数据或少返回数据,都可能导致错误;如果没有设置Content-Length
头,那么服务器在响应完成之后,会关闭链接5,例子
# test_wsgi.py from wsgiref.simple_server import make_server def application(environment, start_response): response = "just for test wsgi" start_response("200 OK", []) error = environment.get("wsgi.errors") error.write("test for error\n") for i in range(5): yield response + "\n" class Middleware: def __init__(self, app): self._app = app def __call__(self, environment, start_response): if environment.get("PATH_INFO") == "/test": start_response("200 OK", []) return ["this is test\n"] return self._app(environment, start_response) if __name__ == "__main__": httpd = make_server("0.0.0.0", 8081, Middleware(application)) httpd.serve_forever()
uWSGI是一个既支持uwsgi协议,又支持http协议的WSGI服务器,它使用C编写的,性能非常好,功能也很全面。Gunicorn是一个用Python编写的WSGI服务器,它只支持http协议。在Python世界中,基本只在两者之间进行选择。
Servlet是Java程序和Servlet引擎交互的接口规范。该规范用于:
这个是在gitbook上找到的一篇Servlet 3.1规范的中文翻译。PDF版本,可以点此下载。
鄙人就职于新浪和高德期间,主要负责一些业务的API开发,尤其是在新浪期间,接触到各种web部署方式,下面以新浪期间的一些经历,进行一下举例说明: