什么是CGI - CGI描述了客户端和服务器程序之间传输数据的⼀种标准,可以让⼀个客户端,从⽹⻚浏览器向执⾏在⽹络服务器上的程序请求数据。
- CGI只是一个接口规范或协议,它的实现和具体的编程语言相关。在2000年以前,CGI通用网关接口盛行,那个时候,Perl是编写CGI的主流语言,以至于一般的CGI程序都是Perl程序。如今,上古巫术Perl早已淡出人们视野,CGI这技术虽然通用,但是实则真正仍在应用的恐怕只有C/C++了。
- 最初,CGI 是在 1993 年由美国国家超级电脑应⽤中⼼(NCSA)为 NCSA HTTPd Web 服务器开发的。这个web服务器使用了Unix Shell环境变量来保存从web服务器传递出去的参数,然后生成一个运行CGI的独立的进程
通用网关接口是什么意思
CGI 即 Common Gateway Interface,译作“通用网关接口”。初次听闻,略感疑惑,实则每个字眼都值得玩味。
common
通用,是一个显著特性。虽然我们听说过Java的Servlet,Python的WSGI。但其实Java、Python都是支持CGI的,不仅如此,其他我们所熟知的语言大多也支持。理论上来说,所有支持标准输出,支持获取环境变量的编程语言都能用来编写CGI程序。
早期的web服务器,只能响应浏览器发来的HTTP镜头资源请求,并将存储在服务器中的静态资源返回给浏览器。随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能直接运行动态脚本。为了解决Web服务器与外部应用程序(CGI程序)之间的数据互通,于是出现了CGI(Common Gateway Interface)通用网关接口。简单理解,可以认为GUI就是Web服务器和运行其上的应用程序进行“交流”的一种约定
PS: 其实,脚本(script)并不一定就是脚本语言编写的。脚本描述的是一类程序的特征:为了完成某一任务,用程序实现批量执行一组常用逻辑的组合。凡是符合这一特征的程序都可称作脚本。当然,时至今日,脚本语言所编写的程序都可以称之为脚本程序,并且其内部逻辑也早已变得并不简单。
Gateway
Gateway,网关。通常意识中,网关一词更多的是硬件层面的概念,但其实与CGI的网关二字之含义也是不谋而合的。称CGI为软件网关也不为过
- 网关,更形象的叫法是协议翻译机。通常与网关输入输出两端通信使用的是不同的协议。即一方是HTTP协议,另一方可能是其他协议,比如企业内部的自定义协议。
- CGI程序通常部署到Web服务器(比如Apache上),Web服务器然后调用CGI程序,关于CGI程序到底如何从Web服务器中获得输入,请继续阅读下一节 Interface。
请注意区分Web Server和后台Server。
Interface(接口)
Interface:Come On。又是一个被国人翻译烂掉的词汇。API(应用程序接口)的I是它,被译作“接口”。UI(用户界面)的I也是它,被译作“界面”。实际他们是同样的意思。我更喜欢“接口”一词。
-
接口,确切而言是“接口协议”,熟悉网络的同学们,肯定都明白“协议”是什么。所谓协议,既是通信双方或多方都共识并遵守的一套规则。就像红灯停,绿灯行,上下车道靠右行。只有全国人民都遵守这个规则,交通才不会出什么乱子。实际上规定绿灯停,红灯行,上下车道靠左行可不可以呢?当然可以,英国日本都是靠左行的,关键是全国人民的认知要一致,这个共识很重要。
-
举几个栗子:TCP/IP这类二进制协议,协议内容的描述是某某字节是干嘛滴,其取值范围是什么,不同取值又是什么含义。HTTP协议是字符协议,这类协议内容的描述就是第一行是啥,第二行是啥……,应该出现啥单词,表示啥意思。
-
其实不管是TCP/IP或是HTTP,都可以统称为“网络协议”,粗浅一点理解就是“描述报文内容详细语义的协议”。而“接口协议”却不然,它不会定义哪些字节该写什么,也不会定义字符的内容规范。CGI其实是构架在HTTP协议之上的。它描述的是另一个维度的共识标准
CGI与CGI程序
CGI是Web服务器和一个独立的进程之间的协议
- 它会将HTTP请求Request的Header头设置进进程的环境变量,HTTP请求的Body正文设置成进程的标准输入
- 进程的标准输出设置为HTTP响应Response,包含Header头和Body正文。
通过CGI接口,Web服务器就能够获取客户端传递的数据,并转交给服务器端的CGI程序处理,然后返回结果给客户端。
简单来说,CGI实际上是一个接口标准,而通常所说的CGI指代其实是CGI程序,也就是说实现了CGI接口标准的程序,只要编程语言具有标准输入、标准输出和环境变量,就可以用来编写CGI程序
- CGI程序通常标准输入(stdin)和标准输出(stdout)进行数据的输入输出,此外CGI程序还通过环境变量来得到输入,操作系统提供了很多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。
- Web服务器和CGI接口又设置了一些环境变量,用来向CGI程序传递一些重要的参数。CGI的GET方法还通过环境变量QUERY_STRING向CGI程序传递Form表单中的数据。
对于一个CGI程序,主要的工作是从环境变量和标准输入中读取数据,然后处理数据,最后向标准输出中输出数据。
- 环境变量:环境变量中存储的叫做Request Meta-Variables,也就是比如QUERY_STRING、PATH_INFO之类的,这些都是由Web服务器通过环境变量传递给CGI程序的,CGI程序也是从环境变量中读取的。
- 标准输出:中存放的往往是用户通过PUTS和POST提交的数据,这些数据也是由Web服务器传递过来的
输入,输出
Web服务器在接收到用户浏览器的HTTP请求,比如请求如下URL:
http://guodongxiaren.me/cgi-bin/helloworld.cgi
- 此时在Web服务器调用helloworld.cgi之前,会把各类HTTP请求中的信息以环境变量的方式写入OS。CGI程序本质上是OS上一个普通的可执行程序,它通常语言本身库函数来获取环境变量,从而获得数据输入。
- 除了环境变量外,另外一个CGI程序获取数据的方式是标准输入(stdin)。比如Post请求一个CGI的URL,那么POST的数据,CGI是通过标准输入来获取到的
而CGI如何构造出数据(比如HTML页面)返回给浏览器的呢?
- 其实CGI本身只要向标准输出去写入数据即可。比如printf、cout,比如System.out.println,又比如print、echo等。因为Web服务器已经做了重定向,将标准输出重定向给Web服务器的与浏览器向链接的socket
- 此时要注意的是,不要以为返回HTML页面,那么直接输出一段HTML代码就OK,注意,此时CGI的输出承担的是HTTP协议的响应部分,因此HTTP响应报头也要自己标准输出出来,比如:
cout<<"Content-Type:text/html\n\n"<<endl;
注意最后要有两个换行符&#xff0c;这是必须有的。因为HTTP协议本身就是一个字符协议。它需要通过一个空行来区分哪里是报头&#xff0c;哪里是实体。在两个换行符&#xff08;即一个空行&#xff09;之后就可以愉快的输出HTML&#xff08;即实体部分&#xff09;了。
关于环境变量
GET请求&#xff0c;它将数据打包放置在环境变量QUERY_STRING中&#xff0c;CGI从环境变量QUERY_STRING中获取数据。
常⻅的环境变量如下表所示&#xff1a;
标准输入
环境变量的大小是由一定的限制的&#xff0c;当需要传送的数据量大时&#xff0c;存储环境变量的空间可能会不足&#xff0c;造成数据接收不完全&#xff0c;甚至无法执行CGI程序。
因此后来有发展处另一种方法&#xff1a;POST&#xff0c;也就是利用IO重新导向的技巧&#xff0c;让CGI程序可以由stdin和stdout直接跟浏览器沟通
当我们使用这种方法传递请求的数据时&#xff0c;web服务器收到数据后会先放在一块输入缓冲区中&#xff0c;并且将数据的大小记录在CONTENT_LENGTH这个环境变量&#xff0c;然后调用CGI程序的stdin指向这块缓冲区&#xff0c;于是我们就可以通过stdin和环境变量CONTENT_LENGTH得到所有的信息&#xff0c;就没有信息大小的限制了
封装
CGI编写Web程序虽然看似解析组装等操作十分繁琐&#xff0c;但其实都有很多第三方的封装来简化这些操作&#xff0c;高级语言的标准库基本都已经做了封装&#xff0c;而针对C&#43;&#43;则有一个还不错的第三方库Cgicc。
关于路由
CGI程序有一不大不小的缺陷&#xff0c;缺乏URL路由的功能&#xff0c;基本上一个CGI都是独立提供给外界访问&#xff0c;一个CGI就是独立的可执行程序。因此不仅CGI的URL比较丑陋&#xff0c;而且容易暴露真实路径。
CGI一般只做上层目录的路由&#xff0c;而这只能交给Web服务器去配置。比如&#xff1a;http://app/hello.cgi 的app目录在物理OS的什么位置可通过Apache去配置。虽然理论上讲CGI程序也可以实现http://app/hello.cgi/abc/def 这种形式的路由。但是基本上没人这样做。
CGI处理流程小结
- web服务器收到客户端&#xff08;浏览器&#xff09;的请求Http Request&#xff0c;启动CGI程序&#xff0c;并通过环境变量、标准输入传递数据
- CGI进程启动解析器、加载配置&#xff08;比如业务相关配置&#xff09;、连接其他服务器&#xff08;比如数据库服务器&#xff09;、逻辑处理等
- CGI进程将处理结果通过标准输出、标准错误&#xff0c;传递给web服务器
- web服务器收到CGI返回的结果&#xff0c;构建Http Response返回给客户端&#xff0c;并杀死CGI进程
web服务器与CGI通过环境变量、标准错误、标准输出、标准错误相互传递数据。在遇到用户连接请求&#xff1a;
- 先要创建CGI子进程&#xff0c;然后CGI子进程处理请求&#xff0c;处理完毕退出这个子进程&#xff1a;&#xff1a;fork-and-execute
- CGI方式是客户端有多少个请求&#xff0c;就开辟多少个子进程&#xff0c;每个子进程都需要启动自己的解释器、加载配置、连接其他服务器等初始化工作&#xff0c;这是CGI进程性能低下的主要原因。当用户请求非常多的时候&#xff0c;会占用大量的内存、CPU等资源&#xff0c;造成性能低下
CGI使得外部程序与web服务器之间交互称为可能。CGI程序运行在独立的进程中&#xff0c;并对每个Web请求建⽴⼀个进程&#xff0c;这种⽅法⾮常容易实现&#xff0c;但效率很差&#xff0c;难以扩展。面对大量请求&#xff0c;进程的大量建立和消亡使操作系统性能大大下降。此外&#xff0c;由于地址空间无法共享&#xff0c;也限制了资源重用
FastCGI 为什么会有FastCGI的出现
既然有这么多现成的库做了封装&#xff0c;那么理应用CGI编写Web的也不少才对。其实不然&#xff0c;这是因为CGI有一大硬伤&#xff1a;
- 每次HTTP请求CGI&#xff0c;Web服务器都要启动一个新的进程去执行一个CGI程序&#xff0c;即颇具Unix特色的fork-and-execute。当用户请求量大的时候&#xff0c;这个fork-and-execute的操作会严重拖慢Web服务器的性能
- 时势造英雄&#xff0c;FastCGI(简称FCGI)技术产生了&#xff0c;简单来说&#xff0c;本身上就是一个常驻内存的进程池技术。由调度器负责将传递过来的CGI请求发送给处理CGI的handler进程来处理。在一个请求处理完成之后&#xff0c;该处理进程不销毁&#xff0c;继续等待下一个请求的到来
当然FCGI其实也并不是什么惊世骇俗的创意&#xff0c;很容易联想到的解决思路。资源池是后台性能优化中的常见套路。Java发明的Servlet技术也是一种常驻内存的网关通信技术&#xff0c;只不过它采用的是多线程而非进程。
什么是FastCGI
快速通⽤⽹关接⼝(Fast Common Gateway Interface&#xff0f;FastCGI)是通⽤⽹关接⼝(CGI)的改进&#xff0c;描述了客户端和服务器程序之间传输数据的⼀种标准。
FastCGI致力于减少Web服务器与CGI程序之间互动的开销&#xff0c;从而使服务器可以同时处理更多的web请求。与为每个请求创建一个新的进程不同&#xff0c;FastCGI使用持续的进程来处理一连串的请求&#xff0c;这些进程有FastCGI进程管理器管理&#xff0c;而不是web服务器。
nginx服务支持FastCGI模式&#xff0c;能够高效的处理动态请求。⽽nginx对应的FastCGI模块为&#xff1a;ngx_http_fastcgi_module。ngx_http_fastcgi_module模块允许将请求传递给FastCGI服务器
FastCGI处理流程
- Web服务器启动时载入初始化FastCGI执行环境。例如IIS、ISAPI、apache mod_fastcgi、ngx_http_fastcgi_module、lighttpd mod_fastcgi。
- FastCGI进程管理器自身初始化&#xff0c;启动多个CGI解释器进程并等待来自Web服务器的连接。启动FastCGI进程时&#xff0c;可以配置以IP和Unix域socket两种方式启动
- 当客户端请求到达web服务器时&#xff0c;web服务器将请求采用socket方式转发FastCGI主进程&#xff0c;FastCGI主进程选择并连接到一个CGI解析器。Web服务器将CGI环境变量和标准输入发送到FastCGI子进程
- FastCGI⼦进程完成处理后将标准输出和错误信息从同⼀socket连接返回Web 服务器。当FastCGI⼦进程关闭连接时&#xff0c;请求便处理完成。
- FastCGI⼦进程接着等待并处理来⾃Web 服务器的下⼀个连接。
由于FastCGI程序并不需要不断的产⽣新进程&#xff0c;可以⼤⼤降低服务器的压⼒并且产⽣较⾼的应⽤效率。它的速度效率最少要⽐CGI 技术提⾼ 5 倍以上。它还⽀持分布式的部署&#xff0c;即FastCGI 程序可以在web 服务器以外的主机上执⾏。
CGI就是所谓的短生存期应用程序&#xff0c;FastCGI是所谓的长生存期应用程序。FastCGI像是一个常驻型的CGI&#xff0c;它可以一直执行着&#xff0c;不会每次都要花费时间去fork一次。
我们知道&#xff0c;CGI可以直接吐出一个html网页&#xff0c;也可以进行各种计算、逻辑处理任务。但随着各类web前后端技术的发展&#xff0c;以及大数据、高并发的Server使用场景越来越多。现代的CGI的用法&#xff0c;在发生变化。越来越多的任务从后端转移到前端&#xff0c;前端页面利用强大的JS承担起更多的责任。
- CGI一般不再用于直接返回html页面&#xff0c;同时将复杂的计算、IO任务下沉到后端&#xff08;后端可以进一步进行路由转发&#xff0c;实现负载均衡&#xff09;。使CGI作为前后端之间的中间层。彼时CGI的职能是完成基本的鉴权以及数据交换。
- Restful风格API的出现&#xff0c;让CGI获得了续命。CGI解析前端请求&#xff0c;再转发给对应后端&#xff1b;然后从后端取回数据&#xff0c;给前端返回XML或JSON。然后前端JS利用XML/JSON中的数据来进行填充。可以绘制出丰富的界面或用作他用。JS可以使用Ajax技术来向后台CGI发起数据请求。Ajax完成的是不需要刷新整个页面就可以加载后端数据&#xff08;比如从数据库中取出&#xff09;。
- 当然实际工程应用中很少严格遵守Restful的学院派定义&#xff0c;比如URL资源使用名词&#xff0c;然后使用各个HTTP的方法&#xff08;GET、POST、PUT、DELETE等&#xff09;表示动词。
- 除了FCGI外&#xff0c;还有SCGI&#xff08;Simple CGI&#xff09;&#xff0c;也是作为CGI的替代协议而产生的&#xff0c;但他与FCGI更像&#xff0c;另外&#xff0c;SCGI在每次完成HTTP应答之后都立即关闭HTTP连接&#xff0c;有点“用完即走”的意思。这个简单的协议更符合Restful API的理念需要。但是名气不大。