【HTTP】Web Server(Web 服务器)、Web Application Server(Web 应用服务器)和 CGI(Common Gateway Interface)的故事

Posted by 西维蜀黍 on 2019-10-28, Last Modified on 2021-09-21

Web Server(Web 服务器)

在网络刚刚盛行的年代(Web 1.0),绝大部分的 Web 资源都是静态的。这意味着,你访问 www.example.com/a.html,你就可以访问 a 页面,而如果访问 www.example.com/b.html,你就可以访问 b页面。显然,信息量是单向的,即网站上有什么,你就只能看什么,而不能修改网站的内容。我们通常把它称为静态网站

狭义的Web Server(Web 服务器)概念中,Web Server只能处理静态页面。

典型的Web Server的例子,就是 Apache HTTP Server,Nginx 还有 LightHttpd。

随着互联网的发展,我们更希望访问一个动态网站(Web 1.5),即可以根据用户传入的信息,动态地生成网页的内容。

这时候,我们就需要一个程序来帮忙处理动态请求(如返回特定用户的信息、获取当前时间等),而静态资源的请求,依然由Web Server来负责。

Web服务器程序会将动态请求转发给帮助程序(helper application),帮助程序处理后,返回处理后的静态结果给Web服务器程序。这样,就避免了 Web 服务器程序需要处理动态页面的问题,如下图:

统一的帮助程序 - CGI (Common Gateway Interface)

随着构建动态网站的需求越来越多,而在上面的这个**帮助程序(helper application)**中,无论我们希望生成怎么样的动态页内容,有一部分工作的处理逻辑都是一样的,比如说HTTP Request的解析,或者HTTP Reponse 的 Header。也就是说,无论我们怎么实现我们的动态网站的功能,你的程序需要解析HTTP请求,我的程序也需要解析。

因此,自然有人会想到 DRY(Don’t Repeat Yourself)原则,即将HTTP Request 的参数解析逻辑放到一个通用的帮助程序中,这个通用的帮助程序就是CGI (Common Gateway Interface)。CGI 其实就是上图中的帮助程序(helper application)

CGI 即 Common Gateway Interface,译作“通用网关接口”。CGI是一种比较原始的开发动态网站的方式。比如用 C++写的一个第三方库 Cgicc。

最终。我们仅仅需要关注业务逻辑的编写。

Django 是一个在 Python 世界中的Web 框架(类似于 Java 中的 Spring Boot 或者C#的 ASP.NET)。我们事实上可以通过使用 Apache HTTP Server的帮助程序来实现 Apache HTTP Server 能够处理动态请求。

其实,严格来说,是当 Apache 接收到了静态资源请求时,就会自己去处理,比如请求 HTML 文件、CSS 文件、JS 文件等;当 Apache 接收到了动态请求时,它会转发这个请求给它的帮助程序来进行处理,这个帮助程序称为 mod_wsgi,see https://docs.djangoproject.com/en/2.2/howto/deployment/wsgi/modwsgi/ for more details。

调用CGI

回到上面的图,当 Web Server 收到用户的请求后,Web Server会解析这个 HTTP Request,然后把这个请求的各种参数写进进程的环境变量,比如REQUEST_METHOD,PATH_INFO之类的。

之后呢,Web Server会调用CGI帮助程序来处理这个请求,这个帮助程序也就是我们的CGI程序了。它会负责生成动态内容,然后返回给Web Server,再由Web Server转交给客户端。

服务器和CGI程序之间通信,一般是通过进程的环境变量管道。具体来说,在Web Server调用CGI程序之前,会把各类HTTP请求中的信息以环境变量的方式写入操作系统。CGI程序本质是操作系统上一个普通的可执行程序,它可以通过语言本身库函数来获取环境变量,从而获得数据输入。

除环境变量外,另外一个CGI程序获取数据的方式就是标准输入(stdin)。如当POST请求Web Server的一个URL时,Web Server会将POST中的数据传入CGI的标准输入。

CGI的不足

这样做虽然很清晰,但缺点也很明显,就是每次有 HTTP 请求时,Web Server都需要fork出一个新的 CGI 进程,这意味着每次都会有一个新的进程产生,因此开销还是比较大的。

原因在于,CGI程序是一个独立的程序,它是可以独立运行的(在提供HTTP请求的情况下),它可以用任何语言来写,包括perl,C,Lua,Python等等。所以对于一个程序,Web Server只能以fork and exec的方式来调用它了。

FastCGI(FCGI)

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

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

Web Application Server(Web 应用服务器) - 现代 CGI

由于现代网站绝大部分均为动态网站,因此有时这个生成返回数据的“帮助程序”(CGI)就内置在了 Web Server 里面,最终 CGI 的概念就不太被提及了。

而从广义的 CGI 的概念来说,所有能动态处理HTTP 请求的 Server 都可以称为 CGI,只不过我们不太习惯使用CGI 这个词。通常我们更多的将其称为Web Application Server(Web 应用服务器)。

而比较严格的来说,Web Server(Web 服务器)只能处理静态的HTTP 请求,而 Web Application Server 能动态地处理HTTP 请求。

虽然,有时我们说的Web Server,其实是指 Web Application Server。在这个上下文中,Web Server就可以动态地处理HTTP 请求了。

而且,有的Web Server提供了 plugins,以支持脚本语言来动态生成 HTTP 内容,比如PHP、Perl 之类。或者,Web Server中就内置了一个 Application Server。比如,比如 IIS(内置了.NET Runtime以运行 ASP.NET Application),提供给 Java EE的 Tomcat。

个人而言,更偏向于把 Web Application Server和Web Server区分开来,则交互关系如下所示:

+--------+       +------------+      +------------------------+
| Client |  -->  | Web Server |  --> | Web Application Server |
+--------+       +------------+      +------------------------+

Example

在 Python 中,Gunicorn and uWSGI就是 Web Application Server;

在 Ruby 中,Unicorn, Puma and Passengee就是 Web Application Server;

在 Java Web中,Web Application Server包括 Web container(或称为servlet container,支持 servlet 和 JSP)、EJB container:

  • 典型的Web container,包括 Apache Tomcat、GlassFish from Oracle、Jetty from the Eclipse Foundation、WildFly (formerly JBoss Application Server)

Web Application

在现代Web Application中,我们通常依赖于框架(framework)来创建我们的Web Application,比如基于 Spring、ASP.NET 或 Django 框架创建的 Web application 等等。

业务逻辑(business logic)就在Web Application中进行编写。这是因为,诸如网络连接、请求缓存、权限管理等很多功能框架本身已经帮我们 cover 了。

Conclusion

**狭义的Web Server概念中,Web Server只能处理静态页面。**而 Web Application Server 能动态地处理HTTP 请求。

我们更偏向于把 Web Application Server 和 Web Server 区分开来,则交互关系如下所示:

+--------+       +------------+      +-------------------------+
| Client |  -->  | Web Server |  --> | Web Application Server |
+--------+       +------------+      +-------------------------+

**而从广义的 CGI 的概念来说,所有能动态处理HTTP 请求的 Web Server 都可以称为 CGI,这其实隐式地(implicitly)**表达了这个Web Server通过 plugin 来通过运行代码来动态地处理情况,或者内置了一个 Web Application Server。

Reference