web与服务端:第二篇 原始的单进程CGI服务器通讯方式
书接上回,我们知道服务器与客户端之间可以通过建立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 | #!/usr/bin/python3 |
存在的问题
运行效率低下
这种方式看起来是几乎很完美的,只要来了一个请求,操作系统直接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 | def application(environ, start_response): |
处理流程:
后面的发展
在WSGI出现以后,绝大部分的python web开发框架都遵守了这套标准,比如说Flask和Django等等。
而在wsgi http服务器中,gunicorn无疑是其中的佼佼者,这是一个pre-fork worker的模型,从Ruby的独角兽(Unicorn )项目移植。该Gunicorn服务器大致与各种Web框架兼容,只需非常简单的执行,轻量级的资源消耗,以及相当迅速。
至于它是如何工作的,且听下回分解。