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.pngAlias /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版本,可以点此下载。