-- TOC --
本文简要介绍CGI,FastCGI,SCGI,WSGI,以求达到理解清楚其概念的程度。
CGI,Common Gateway Interface
,是一种Web Server与本机上的外部程序通信的机制,用来产生动态网页内容。它是最早的动态网页解决方案。
每当客户端访问一个指向CGI程序的URL,Web Server收到后,就会创建一个新的子进程,以执行此CGI程序。CGI程序执行完毕后,向Web Server输出它的执行结果,子进程退出,然后Server将这些结果返回给客户端浏览器显示。
可能是因为很多CGI程序都是用脚本语言写的,所以很多人会说CGI脚本,但CGI可以是用C写的程序。
CGI模式的开销相对较大,因为它是经典的Fork-and-Execution
模式,Web Server会为每一个CGI脚本的请求,以创建子进程的方式执行。所以,当请求非常多的时候,服务器上运行CGI脚本的子进程数量也会很多,而且子进程不停地在创建和退出。现在一般生产环境不会使用CGI模式,但是测试环境用的很多,因为很方便。Web Server对CGI的支持是最广泛的,而且配置相对简单。
上图中红色的XX,表示CGI子进程处理完毕后退出。
CGI还有一个优点,即你可以使用任何编程语言来编写CGI脚本或程序,没有限制。CGI程序的stdout被重定向,写入stdout,都是返回给Web Server的内容。
配置CGI: 如何在Httpd服务器上配置CGI
FastCGI,就是快速的CGI。
CGI的慢,在于收到请求后再创建和执行子进程。而FastCGI不用等待请求到来后再创建子进程,而是直接先启动一大堆子进程,然后等待请求的到来。这些子进程处理完请求后,并不会退出,而是继续等待下一个请求。因此,这样的机制就省掉了不断地重复创建子进程的消耗,整体效率比CGI要高,但是Web Server也会一直占用更多的内存。
FastCGI的实现,有一个Server,用来接收Web Server转发的请求。FCGI is a lot like proxying. 在CGI中,Web Server复杂根据请求启动CGI进程。在FCGI中,Web Server只是转发请求给FCGI Server Process。
上图中的socket,应该是指UNIX socket,用于同一主机内进程间通信。
Simple CGI,简单的CGI,它是FastCGI在精简数据协议和响应过程后的产物。
SCGI设计目的是为了适应越来越多基于AJAX或REST的HTTP请求,而做出更快更简洁的应答。并且SCGI约定,当服务器返回对一个HTTP协议请求响应后,立刻关闭该HTTP的TCP连接。所以不难看出,SCGI更加适合于普遍意义上SOA所提倡的“请求-忘记”这种通信模式。
似乎网络上关于SCGI的内容不多,上面这段话好多地方都能看到。更多关于SCGI的内容:SCGI - http://en.wikipedia.org/wiki/SCGI
从原理上来看,SCGI和FastCGI类似,二者的性能并无多大差别。但比起后者复杂的协议内容来说,SCGI移除了许多非必要的功能,看起来十分简洁,且实现复杂度更低。
与FastCGI类似,一个SCGI服务器可以动态创建服务器子进程用于处理更多请求(处理完毕将转入睡眠),直至达到配置的子进程上限。当获得Web服务器转发的请求时,SCGI服务器进程会将其转发至子进程,并由子进程运行CGI程序处理该请求。此外,SCGI还能自动销毁退出和崩溃的子进程,具有良好的稳定性。
WSGI是Python官方定义的一个Web Server与Python App之间的接口。在Web Server与Python程序之间,一般都还有一个Web Framework,比如Django,Flask。这些框架都是基于WSGI协议,Web Server要支持WSGI,现在基本都需要安装额外的模块。
Python官方之所以这样做,是为了子系统之间的解耦!Python的Web框架不必将自己限制在某一种特定的接口上,Python App在编写的时候,也不需要过度考虑使用什么样的Web Server。各自通过标准接口通信,各自发展壮大。
WSGI是一个函数调用接口规范,因此它不涉及在进程还是在线程中调用的问题,都可以。使用CGI方式可以调用Flask框架下的App,Httpd使用mod_wsgi模块,就是在线程中调用Python。
wsgi只是一个接口规范:
The WSGI application interface is implemented as a callable object: a function, a method, a class or an instance with an object.__call__()
method. That callable must:
(1). accept two positional parameters:
接口有两个参数,第一个是dict对象,保存着各种环境变量;第二个是一个callback,用来传递status code and msg和http headers。
(2). return the response body to the server as byte-strings wrapped in an iterable.
将body转换成byte-string,并放在一个可迭代对象中返回。
The application skeleton:
# The application interface is a callable object
def application ( # It accepts two arguments:
# environ points to a dictionary containing CGI like environment
# variables which is populated by the server for each
# received request from the client
environ,
# start_response is a callback function supplied by the server
# which takes the HTTP status and headers as arguments
start_response
):
# Build the response body possibly
# using the supplied environ dictionary
response_body = 'Request method: %s' % environ['REQUEST_METHOD']
# HTTP response code and message
status = '200 OK'
# HTTP headers expected by the client
# They must be wrapped as a list of tupled pairs:
# [(Header name, Header value)].
response_headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))
]
# Send them to the server using the supplied function
start_response(status, response_headers)
# Return the response body. Notice it is wrapped
# in a list although it could be any iterable.
return [response_body.encode()]
环境变量中,
REQUEST_METHOD
指定了GET或者POST,PATH_INFO
指定了URL,QUERY_STRING
是?
后面的那个字符串,用来传递参数的,可以用cgi.parse_qs()和cgi.escape()来安全获取,POST数据放在wsgi.input
字段中,但有可能CONTENT_LENGTH
不一定存在!
WSGI是Python生态圈内特有的名字,它指一个接口规范,Web Server通过此接口规范与Web App交互。
下面是一个简易的WSGI APP的实现,将请求的部分内容返回输出:
$ cat my_wsgi_app.py
#!/usr/bin/env python3
import urllib.parse as urlparse
from wsgiref.simple_server import make_server
def wsgi_app(environ, start_response):
# USER AGENT
user_agent = environ['HTTP_USER_AGENT']
# GET or POST
request_method = environ['REQUEST_METHOD']
# PATH_INFO
path_info = environ['PATH_INFO']
# QUERY_STRING
query_string = environ['QUERY_STRING']
qs = urlparse.parse_qs(query_string)
# CONTENT_LENGTH
try:
content_length = int(environ.get('CONTENT_LENGTH', 0))
except (ValueError):
content_length = 0
# CONTENT_TYPE
content_type = environ['CONTENT_TYPE']
# When the method is POST the variable will be sent
# in the HTTP request body which is passed by the WSGI server
# in the file like wsgi.input environment variable.
if request_method == 'POST':
request_body = environ['wsgi.input'].read(content_length)
else:
request_body = b''
#
response = []
response.append('USER_AGENT:' + user_agent)
response.append('REQUEST_METHOD:' + request_method)
response.append('PATH_INFO:' + path_info)
response.append('QUERY_STRING:' + query_string)
response.append('qs:' + str(qs))
response.append('CONTENT_LENGTH:' + str(content_length))
response.append('CONTENT_TYPE:' + content_type)
response.append('REQUEST_BODY:' + request_body.decode())
response = '\n'.join(response) + '\n'
status = '200 OK'
response_headers = [
('Content-Type', 'text/plain'),
('Content-Length', str(len(response)))
]
start_response(status, response_headers)
return [response.encode()]
# Instantiate the server
httpd = make_server (
'localhost', # The host name
8051, # A port number where to wait for the request
wsgi_app # The application object name, in this case a function
)
httpd.serve_forever()
CGI最简单,开发人员测试必备!WSGI是Python独有的,Python程序员如果做Web开发,必备。
本文链接:https://cs.pynote.net/net/202110015/
-- EOF --
-- MORE --