书接上回,我们知道服务器与客户端之间可以通过建立tcp连接,使用套接字进行通信。这个时候假如说我们有非常多的客户端需要访问服务器,服务器就需要不断的开启进程哪怕我们的内存处理机制再优秀,也无法处理数量如此庞大的客户端群体。这个时候,人们就想出来很多方案,假如我们只是请求一个静态的页面,就像这个博客一样,只要看上面的内容,就可以搭建一个分发网络,专门传送静态文件,静态页面,发送完成后立即释放客户端,这样可以有效的减轻服务器的压力,而且经过专门的存储优化,可以使得速度和连接数量大大提升。但是我们还是需要开启应用程序来处理那些必须与服务器交互的web请求,比如数据库服务器等等。这些web应用程序与客户端的通信,他们所依赖的协议就叫做cgi协议。

前言

所谓cgi协议(common gateway interface)-通用网关接口,我们很难见名思意,啥网关,啥接口?啊?所以我们先不管这些。
简单来说,就是客户端与服务器的web应用程序之间动态请求需要遵从的一种通信协议。通过cgi协议,再结合web应用程序就可以处理用户请求啦。

为啥要这么做?

在上一章节,我们知道,搭建一个简单的socket套接字服务器,就可以与客户端互发消息,但是结尾也提到,在一对多通信中,服务器是无法处理这么大量的请求的。而且一般服务器上要实现的功能很多,很难知道你是具体想与哪个应用程序通信,所以这个时候就需要有一种设计规范,来连接客户端与web应用程序。

工作方式

简单版的cgi协议与服务器的php程序通信时候的工作方式如下所示:

网上抄来的图

假如,我们在百度上搜索一个cgi,对应的url是:
https://www.baidu.com/s?wd=cgi&ie=UTF-8
当百度的服务器知道了你的请求之后会解析url,知道了/s是要搜索,随后调用搜索程序,然后获取到了要搜索的值,再返回给用户。通过socket套接字和cgi的环境变量传递给web程序处理之后,web程序再原路将处理的结果返回到客户端,然后客户端完成接收。

实际上,能够运行cgi的程序有非常多种,像c/c++,python,php,java等都是可以的。常用于编写CGI的语言有perl、php、python等,java也一样能写,但java的servlet完全能实现CGI的功能,且更优化、更利于开发。

写一个简单的cgi程序

通过Apache等服务器配置软件,简单的配置一个网页之后,简单的运行一下

具体方法请参照菜鸟教程:点这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/python3
# -*- coding: UTF-8 -*-

print("Content-type:text/html")
print() # 空行,告诉服务器结束头部
print('<html>')
print('<head>')
print('<meta charset="utf-8">')
print('<title>Hello World - 我的第一个 CGI 程序!</title>')
print('</head>')
print('<body>')
print('<h2>Hello World! 我是来自菜鸟教程的第一CGI程序</h2>')
print('</body>')
print('</html>')

存在的问题

运行效率低下

这种方式看起来是几乎很完美的,只要来了一个请求,操作系统直接fork()一个web程序来处理,然后返回结果之后再回收。又省内存又方便。

BUT,其中存在一个致命的问题,就是随着请求的不断增多,fork()进程的开销也越来越大,所以cgi实在是慢,而且是非常慢

没有路由功能

CGI程序有一不大不小的缺陷,缺乏URL路由的功能,基本上一个CGI都是独立提供给外界访问,一个CGI就是独立的可执行程序。因此不仅CGI的URL比较丑陋,而且容易暴露真实路径

CGI一般只做上层目录的路由,而这只能交给Web服务器去配置。比如:http://app/hello.cgi 的app目录在物理OS的什么位置可通过Apache去配置。虽然理论上讲CGI程序也可以实现http://app/hello.cgi/abc/def 这种形式的路由。但是基本上没人这样做。

fastcgi

每次HTTP请求CGI,Web服务器都有启动一个新的进程去执行这个CGI程序,即颇具Unix特色的fork-and-execute。当用户请求量大的时候,这个fork-and-execute的操作会严重拖慢Web服务器的性能。

时势造英雄,FastCGI(简称FCGI)技术应运而生。简单来说,其本质就是一个常驻内存的进程池技术,由调度器负责将传递过来的CGI请求发送给处理CGI的handler进程来处理。在一个请求处理完成之后,该处理进程不销毁,继续等待下一个请求的到来。

当然FCGI其实也并不是什么惊世骇俗的创意,很容易联想到的解决思路。资源池是后台性能优化中的常见套路。Java发明的Servlet技术也是一种常驻内存的网关通信技术,只不过它采用的是多线程而非进程。

FastCGI 作为长期存在的应用服务,他的效率可以比cgi提升5倍以上。

mod_python

与fastcgi不同,Apache组织为了提升python cgi的性能,开发了mod_python,使得python脚本可以被嵌入到Apache程序中。

Apache分阶段的处理请求(比方说:读取请求,解析header, 检查存取路径,等等)。这些阶段能被称为"处理器"(handler)的函数实现。传统上, "处理器"是由C语言编写,并编译成Apache的模块。Mod_python提供了一个通过Python写的Apache处理器的来扩展Apache功能的方法。

为了轻松地从CGI移植,一个标准的mod_python处理器提供了模拟的CGI环境,允许用户在不对代码做任何修改的情况下,使遗留的脚本运行在mod_python下(大多数情况)。

WSGI

背景

经过一段时间的发展,从socket到cgi,python的web服务到底应该如何开发,这困扰了很多人,以前的Python应用程序通常是为CGI,FastCGI,mod_python中的一个而设计,甚至是为特定Web服务器的自定义的API接口而设计的。

所以,python官方基于现存的cgi标准,设计了WSGI,PythonWeb服务器网关接口(Python Web Server Gateway Interface,缩写为WSGI)。WSGI(有时发音作’wiz-gee’)是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。

WSGI 没有官方的实现, 因为WSGI更像一个协议. 只要遵照这些协议,WSGI应用(Application)都可以在任何服务器(Server)上运行, 反之亦然。

WSGI标准在 PEP 333 中定义并被许多框架实现,其中包括现广泛使用的django框架。文档地址:https://peps.python.org/pep-3333/

注:我认为WSGI标准的作用更多的是规范化服务器与python web程序之间的通信标准,从而提升python web程序的开发效率以及可移植性。

具体内容

WSGI描述了Server与Framework之间通信的规范,简单来说,WSGI规范了以下几项内容:

  • WSGI协议主要包括server和application两部分,server负责接受客户端请求并进行解析,然后将其传入application,客户端处理请求并将响应头和正文返回服务器(严格说来,还有一个模块叫做中间件middleware,但中间件也同样使用上述两种接口进行通讯)
  • 从application的角度来说,它应当是一个可调用的对象(实现了__call__ 函数的方法或者类),它接受两个参数:environ和start_response,其主要作用就是根据server传入的environ字典来生成一个“可迭代的”http报文并返回给server
    从server的角度来说,其主要工作是解析http请求,生成一个environ字典并将其传递给可调用的application对象;另外,server还要实现一个start_response函数,其作用是生成响应头,start_response作为参数传入application中并被其调用。

实现

一个简单的实现。

1
2
3
4
5
6
7
def application(environ, start_response):
start_response("200 0K",[("Content-type", "text/plain")])
return ["Hello World!",]
if __name__ == "__main__":
from Wsgiref.simple_ server import make_server
server = make_server('localhost',8080,application)
server.serve_forever()

处理流程:
python-web-2-2022-04-05-10-22-54

后面的发展

在WSGI出现以后,绝大部分的python web开发框架都遵守了这套标准,比如说Flask和Django等等。

python-web-2-2022-04-05-10-31-48

而在wsgi http服务器中,gunicorn无疑是其中的佼佼者,这是一个pre-fork worker的模型,从Ruby的独角兽(Unicorn )项目移植。该Gunicorn服务器大致与各种Web框架兼容,只需非常简单的执行,轻量级的资源消耗,以及相当迅速。

至于它是如何工作的,且听下回分解。