如何理解 CGI, WSGI?
添加评论
按投票排序
按时间排序
9 个回答
知乎用户、Spike Thomas、贡瑞可
等人赞同
我对 CGI 的理解是利用程序的标准输入输出流,完成 HTTP 通信。HTTP 是文本协议,每次请求的文本以标准输入流的形式进入服务器端 CGI 程序,创建进程;然后进程的标准输出流作为响应 。
WSGI 和 CGI 原理极其相似,但是是完全不同的实现。WSGI 是 Python 专用的协议,也是输入&输出的方式传输文本流,但不是创建进程,而是对一个 WSGI 程序(callable 的对象,可以是函数也可以是实现了 __call__ 的对象),将 request 作为参数传入(不再是纯文本,而是经过包装),同样将经过包装的 response 作为响应返回。request/response 的包装由 Python 标准库提供。
二者都是标准,都有诸多实现。CGI 最为广泛,无论是二进制程序、perl 脚本、python 脚本还是 PHP 都可以以这样的原理提供 HTTP 服务;WSGI 在 Python 界也是公共标准,主流 Web 框架基本都有实现,例如 Bottle 的 bottle.Bottle (Application 对象)就实现了 WSGI 协议(见 __call__ 方法)。
WSGI 和 CGI 原理极其相似,但是是完全不同的实现。WSGI 是 Python 专用的协议,也是输入&输出的方式传输文本流,但不是创建进程,而是对一个 WSGI 程序(callable 的对象,可以是函数也可以是实现了 __call__ 的对象),将 request 作为参数传入(不再是纯文本,而是经过包装),同样将经过包装的 response 作为响应返回。request/response 的包装由 Python 标准库提供。
二者都是标准,都有诸多实现。CGI 最为广泛,无论是二进制程序、perl 脚本、python 脚本还是 PHP 都可以以这样的原理提供 HTTP 服务;WSGI 在 Python 界也是公共标准,主流 Web 框架基本都有实现,例如 Bottle 的 bottle.Bottle (Application 对象)就实现了 WSGI 协议(见 __call__ 方法)。
WSGI, Web Server Gateway Interface
如全称代表的那样,WSGI不是服务器,不是API,不是Python模块,更不是什么框架,而是一种服务器和客户端交互的 接口规范!
更具体的规范说明请搜索“PEP 3333”。
在WSGI规范下,web组件被分成三类:client, server, and middleware.
WSGI apps(服从该规范的应用)能够被连接起来(be stacked)处理一个request,这也就引发了中间件这个概念,中间件同时实现c端和s端的接口,c看它是上游s,s看它是下游的c。
WSGI的s端所做的工作 仅仅是接收请求,传给application(做处理),然后将结果response给middleware或client.除此以外的工作都交给中间件或者application来做。
如全称代表的那样,WSGI不是服务器,不是API,不是Python模块,更不是什么框架,而是一种服务器和客户端交互的 接口规范!
更具体的规范说明请搜索“PEP 3333”。
在WSGI规范下,web组件被分成三类:client, server, and middleware.
WSGI apps(服从该规范的应用)能够被连接起来(be stacked)处理一个request,这也就引发了中间件这个概念,中间件同时实现c端和s端的接口,c看它是上游s,s看它是下游的c。
WSGI的s端所做的工作 仅仅是接收请求,传给application(做处理),然后将结果response给middleware或client.除此以外的工作都交给中间件或者application来做。
正好最近在学习CGI。
CGI是比较原始的开发动态网站的方式。你可以想象一下,一个网站的动态内容肯定是程序生成的,光是静态的html页面无法达到这个效果。那么,这个程序就需要接受客户端的请求,然后进行相应处理,再返回给客户端,客户端和服务端的通信当然是通过HTTP协议。
然后我们会发现,这个程序在处理客户端请求的时候,大部分时候会进行很多重复的工作,比如说HTTP请求的解析。也就是说,你的程序需要解析HTTP请求,我的程序也需要解析。
于是为了DRY原则,Web服务器诞生了。(以下所说的都是CGI的工作模式)
于是Web服务器可以解析这个HTTP请求,然后把这个请求的各种参数写进进程的环境变量,比如
REQUEST_METHOD,PATH_INFO之类的。之后呢,服务器会调用相应的程序来处理这个请求,这个程序也就是我们所要写的CGI程序了。它会负责生成动态内容,然后返回给服务器,再由服务器转交给客户端。服务器和CGI程序之间通信,一般是通过进程的环境变量和管道。
这样做虽然很清晰,但缺点就是每次有请求,服务器都会fork and exec,每次都会有一个新的进程产生,开销还是比较大的。
原因在与CGI程序是一个独立的程序,它是可以独立运行的(在提供HTTP请求的情况下),它可以用几乎所有语言来写,包括perl,c,lua,python等等。所以对于一个程序,服务器只能以fork and exec的方式来调用它了。
我所理解的CGI差不多就是这样。
CGI是比较原始的开发动态网站的方式。你可以想象一下,一个网站的动态内容肯定是程序生成的,光是静态的html页面无法达到这个效果。那么,这个程序就需要接受客户端的请求,然后进行相应处理,再返回给客户端,客户端和服务端的通信当然是通过HTTP协议。
然后我们会发现,这个程序在处理客户端请求的时候,大部分时候会进行很多重复的工作,比如说HTTP请求的解析。也就是说,你的程序需要解析HTTP请求,我的程序也需要解析。
于是为了DRY原则,Web服务器诞生了。(以下所说的都是CGI的工作模式)
于是Web服务器可以解析这个HTTP请求,然后把这个请求的各种参数写进进程的环境变量,比如
REQUEST_METHOD,PATH_INFO之类的。之后呢,服务器会调用相应的程序来处理这个请求,这个程序也就是我们所要写的CGI程序了。它会负责生成动态内容,然后返回给服务器,再由服务器转交给客户端。服务器和CGI程序之间通信,一般是通过进程的环境变量和管道。
这样做虽然很清晰,但缺点就是每次有请求,服务器都会fork and exec,每次都会有一个新的进程产生,开销还是比较大的。
原因在与CGI程序是一个独立的程序,它是可以独立运行的(在提供HTTP请求的情况下),它可以用几乎所有语言来写,包括perl,c,lua,python等等。所以对于一个程序,服务器只能以fork and exec的方式来调用它了。
我所理解的CGI差不多就是这样。
一个HTTP请求过来,一直到最后返回一个页面,经过哪些环节。把整个流程梳理清楚了,才能真正的理解这几个概念的位置,以及他们之间的关系。
有一篇很好的文章( WSGI、flup、fastcgi、web.py的关系),直接转贴(感谢原作者)
有一篇很好的文章( WSGI、flup、fastcgi、web.py的关系),直接转贴(感谢原作者)
Apache/lighttpd: 相当于一个request proxy,根据配置,把不同的请求转发给不同的server处理,例如静态的文件请求自己处理,这个时候它就像一个web server,对于fastcgi/python这样的请求转发给flup这样的Server/Gateway进行处理
flup: 一个用python写的web server,也就是cgi中所谓的Server/Gateway,它负责接受apache/lighttpd转发的请求,并调用你写的程序 (application),并将application处理的结果返回到apache/lighttpd
fastcgi: apache/lighttpd的一个模块,虽然flup可以作为一个独立的web server使用,但是对于浏览器请求处理一般都交给 apache/lighttpd处理,然后由apache/lighttpd转发给flup处理,这样就需要一个东西来把apache/lighttpd跟flup联系起来,这个东西就是fastcgi,它通过环境变量以及socket将客户端请求的信息传送给flup并接收flup返回的结果
web.py: 应该说有了上面的东西你就可以开始编写你的web程序了,但是问题是你就要自己处理浏览器的输入输出,还有cookie、session、模板等各种各样的问题了,web.py的作用就是帮你把这些工作都做好了,它就是所谓的web framework,另外一个出名的是django,不过感觉太复杂了,web.py差不多就够用了
WSGI : 除了flup Server/Gateway外还有很多其他人的写的Server/Gateway, 这个时候就会出问题了,如果你在flup上写了一个程序,现在由于各种原因你要使用xdly了,这个时候你的程序也许就要做很多痛苦的修改才能使用 xdly server了,WSGI就是一个规范,他规范了flup这个服务应该怎么写,应该使用什么方式什么参数调用你写的程序(application)等,当然同时也规范你的程序应该怎么写了,这样的话,只要flup跟xdly都遵守WSGI的话,你的程序在两个上面都可以使用了,flup就是一个WSGI server
CGI(Common Gateway Interface)可以说是一种替代用户直接访问服务器上文件而诞生的一种“代理”,用户通过CGI来获取动态数据或文件等。
从最原始的意义上来说,CGI是一种设计思想,其最早的实现是每次请求都直接调用操作系统来创建进程、销毁进程,这种程序虽然效率不高但是给WEB数据动态访问提供了很好的思路。
现在经过改进的CGI效率已经大大提高,尤其是fastCGI等实现。
而WSGI是Web Server Gateway Interface的简称,从名字上看和CGI一定有渊源。事实上,由于之前的CGI程序和编写WEB服务所用的语言往往是不同的(CGI用C,WEB用PHP等),WSGI的其中一个目的就是让用户可以用统一的语言编写前后端,WSGI参考了CGI的设计,对CGI的设计思想进行了进一步包装。
参考: http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
这么做执行效率当然不高,不过根据《黑客与画家》最后的预言,这些效率是值的牺牲了,未来谁知道呢。
总结来说:
1、CGI是一种为用户动态提供所需数据的设计思想,它有很多各种不同语言的实现。
2、WSGI是Python对CGI进行的一种包装,核心使用Python实现,具体实现通常来说也需要使用Python,目前Django、Google webapp框架都实现了WSGI。
HTH.
从最原始的意义上来说,CGI是一种设计思想,其最早的实现是每次请求都直接调用操作系统来创建进程、销毁进程,这种程序虽然效率不高但是给WEB数据动态访问提供了很好的思路。
现在经过改进的CGI效率已经大大提高,尤其是fastCGI等实现。
而WSGI是Web Server Gateway Interface的简称,从名字上看和CGI一定有渊源。事实上,由于之前的CGI程序和编写WEB服务所用的语言往往是不同的(CGI用C,WEB用PHP等),WSGI的其中一个目的就是让用户可以用统一的语言编写前后端,WSGI参考了CGI的设计,对CGI的设计思想进行了进一步包装。
参考: http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
这么做执行效率当然不高,不过根据《黑客与画家》最后的预言,这些效率是值的牺牲了,未来谁知道呢。
总结来说:
1、CGI是一种为用户动态提供所需数据的设计思想,它有很多各种不同语言的实现。
2、WSGI是Python对CGI进行的一种包装,核心使用Python实现,具体实现通常来说也需要使用Python,目前Django、Google webapp框架都实现了WSGI。
HTH.
简单看了一下PEP-0333,谈谈个人见解:
WSGI里的组件分为『Server』,『Middleware』和『Application』三种,其中的『Middleware』是『设计模式』里的Decorator(装饰器)。
WSGI规范在PEP-333里讲得很详细: PEP 0333 -- Python Web Server Gateway Interface v1.0 ,但我觉得比理解规范更重要的,是理解其设计目的和工作原理。
WSGI规范写得有点绕,之所以绕, 主要原因可能是没有用『类型提示(Type Hints)』,如果用强类型OOP语言的那种『Interface』和『UML』来解释会清晰很多。下面我试试用这种带有『类型提示』的风格简单讲讲WSGI的原理。
首先是『Application』
如果把它算作接口的话,它 要能被直接调用(Callable),换句话说,它应该是一个函数或者定义了『__call__』方法的对象,『__call__』方法的签名是这样的:
为了便于叙述,这里用的『类型提示』没有严格遵从Python语法。这里大致的意思是 __call__ 方法需要两个参数,『environ』是一个键(Key)为 String 类型,值(Value)为 任意(Any)类型的字典,『start_response』则是一个函数(实际上为了兼容已有框架,它也可以是一个函数工厂,详见PEP-0333)。 __call__ 方法返回一个可迭代的对象。
可以这样理解,如果想正确调用『Application』,就必须知道两样东西,『数据』和『上层处理方法』,『数据』是那个environ变量(CGI里就叫这个名字),『上层处理方法』是那个start_response 回调函数,这两个参数,须在Server调用Application的时候,传给Application。或者说Application依赖于『数据』和『上层处理方法』,这两个依赖由Server『注入』给Application(称作『依赖注入 DI』)。
容易困惑的一点是为什么『上层处理方法』要通过参数传给Application,直接在Server里处理好了,把处理好的结果传给Application不是更简单吗?的确这样做似乎也可以,不过把『上层处理方法』作为回调函数传给Application,可以由Application本身控制该方法的调用时机,是更加灵活的一种方案(称作『控制反转 IoC』)。
接下来是『Middleware』
为了简化代码,我们先把『Application』单独定义为一个类型:
其实就是前面那个 __call__ 方法的签名,至于Application究竟是什么,叫它『函数』,或者『实现了__call__方法的 对象』,『实现了__init__方法的类』都可以,总之都可以通过『Application()』去使用,我们暂且统称为『Callable对象』。
『Middleware』本质上是一个『装饰器 Decorator』,和Application类似它也是一个『Callable对象』,如果它有『__call__』方法,其签名应该是这样的:
这个Middleware返回的类型是一个被『装饰』过的Application(transformedApp变量),这个transformedApp所依赖的『environ』和『start_response』可以被当前Middleware所在上层的Server/Middleware所注入。
再看 这个Middleware的参数,它也是一个Application(app变量),所以Middleware本身的一些附加的逻辑和数据也可以通过app的参数注入到下层的Application里。
Middleware是可以嵌套使用的,比如有『mw1』和『mw2』两个Middleware和『app』一个Application,就可以通过
返回一个新的Application,如果mw1和mw2是相互独立的,嵌套顺序理论上也可以互换。
也可以使用Python的『Decorator』语法,直接装饰app:
最后是『Server』
好像能说的不多,这一层更像是一个『适配器 Adapter』,比如PEP-0333里面给的例子就是一个『CGI』的Adapter。它从 os.environ 或 sys.stdin(CGI规定的输入方式) 中获取Request数据,自定义了start_response和write函数,在其中使用 sys.stdout(CGI规定的输出方式) 进行响应。它把environ和start_response传给Application进行调用,然后遍历Application返回的Iterable,使用write函数把结果写到sys.stdin里。 写进sys.stdin,其实就是和更上层的,也就是Web Server(例如Apache/Nginx)进行通讯,Web Server通过Socket获取到内容以后,就可以最终把Response返回给用户了。
WSGI里的组件分为『Server』,『Middleware』和『Application』三种,其中的『Middleware』是『设计模式』里的Decorator(装饰器)。
WSGI规范在PEP-333里讲得很详细: PEP 0333 -- Python Web Server Gateway Interface v1.0 ,但我觉得比理解规范更重要的,是理解其设计目的和工作原理。
WSGI规范写得有点绕,之所以绕, 主要原因可能是没有用『类型提示(Type Hints)』,如果用强类型OOP语言的那种『Interface』和『UML』来解释会清晰很多。下面我试试用这种带有『类型提示』的风格简单讲讲WSGI的原理。
首先是『Application』
如果把它算作接口的话,它 要能被直接调用(Callable),换句话说,它应该是一个函数或者定义了『__call__』方法的对象,『__call__』方法的签名是这样的:
def __call__(environ: Dict[String, Any],
start_response: (String, List) -> Any): Iterable[String]
为了便于叙述,这里用的『类型提示』没有严格遵从Python语法。这里大致的意思是 __call__ 方法需要两个参数,『environ』是一个键(Key)为 String 类型,值(Value)为 任意(Any)类型的字典,『start_response』则是一个函数(实际上为了兼容已有框架,它也可以是一个函数工厂,详见PEP-0333)。 __call__ 方法返回一个可迭代的对象。
可以这样理解,如果想正确调用『Application』,就必须知道两样东西,『数据』和『上层处理方法』,『数据』是那个environ变量(CGI里就叫这个名字),『上层处理方法』是那个start_response 回调函数,这两个参数,须在Server调用Application的时候,传给Application。或者说Application依赖于『数据』和『上层处理方法』,这两个依赖由Server『注入』给Application(称作『依赖注入 DI』)。
容易困惑的一点是为什么『上层处理方法』要通过参数传给Application,直接在Server里处理好了,把处理好的结果传给Application不是更简单吗?的确这样做似乎也可以,不过把『上层处理方法』作为回调函数传给Application,可以由Application本身控制该方法的调用时机,是更加灵活的一种方案(称作『控制反转 IoC』)。
接下来是『Middleware』
为了简化代码,我们先把『Application』单独定义为一个类型:
type Application = (Dict[String, Any],
(String, List) -> Any) -> Iterable[String]
其实就是前面那个 __call__ 方法的签名,至于Application究竟是什么,叫它『函数』,或者『实现了__call__方法的 对象』,『实现了__init__方法的类』都可以,总之都可以通过『Application()』去使用,我们暂且统称为『Callable对象』。
『Middleware』本质上是一个『装饰器 Decorator』,和Application类似它也是一个『Callable对象』,如果它有『__call__』方法,其签名应该是这样的:
def __call__(app: Application): transformedApp: Application
这个Middleware返回的类型是一个被『装饰』过的Application(transformedApp变量),这个transformedApp所依赖的『environ』和『start_response』可以被当前Middleware所在上层的Server/Middleware所注入。
再看 这个Middleware的参数,它也是一个Application(app变量),所以Middleware本身的一些附加的逻辑和数据也可以通过app的参数注入到下层的Application里。
Middleware是可以嵌套使用的,比如有『mw1』和『mw2』两个Middleware和『app』一个Application,就可以通过
mw1(mw2(app))
返回一个新的Application,如果mw1和mw2是相互独立的,嵌套顺序理论上也可以互换。
也可以使用Python的『Decorator』语法,直接装饰app:
@mw1
@mw2
def app(environ, start_response):
...
最后是『Server』
好像能说的不多,这一层更像是一个『适配器 Adapter』,比如PEP-0333里面给的例子就是一个『CGI』的Adapter。它从 os.environ 或 sys.stdin(CGI规定的输入方式) 中获取Request数据,自定义了start_response和write函数,在其中使用 sys.stdout(CGI规定的输出方式) 进行响应。它把environ和start_response传给Application进行调用,然后遍历Application返回的Iterable,使用write函数把结果写到sys.stdin里。 写进sys.stdin,其实就是和更上层的,也就是Web Server(例如Apache/Nginx)进行通讯,Web Server通过Socket获取到内容以后,就可以最终把Response返回给用户了。