文章目录
- 1 CGI
- 1.1 CGI原理
- 1.2 输入/出
- 1.3 环境变量
- 1.3.1 与请求相关的环境变量
- 1.3.2 与服务器相关的环境变量
- 1.3.3 与客户端相关的环境变量
- 1.3.4 详细说明
-
- 1.4 各种语言的封装
- 1.4.1 C语言
- 1.4.2 C++
- 1.4.3 PHP
- 1.4.4 Perl
- 1.4.5 Python
- 1.5 现代CGI的编程范式
- 1.6 缺点
- 2 FastCGI
-
- 3 Servlet
- 3.1 Servlet有用的资源
- 3.2 Servlet容器
- 4 WSGI
- 4.1 WSGI构成
- 4.1.1 WSGI Server/gateway
- 4.1.2 WSGI Application
- 4.1.3 WSGI MiddleWare
- 参考资料
简述下Web开发基础概念,从早期的CGI,FastCGI,再到Servlet,以及Python的WSGI,uWSGI等。
1 CGI
CGI:通用网关接口(Common Gateway Interface)是一个Web服务器主机提供信息服务的标准接口。通过CGI接口,Web服务器就能够获取客户端提交的信息,转交给服务器端的CGI程序进行处理,最后返回结果给客户端。
早期的Web服务器,只能响应浏览器发来的HTTP静态资源的请求,并将存储在服务器中的静态资源返回给浏览器。随着Web技术的发展,逐渐出现了动态技术,但是Web服务器并不能够直接运行动态脚本,为了解决Web服务器与外部应用程序(CGI程序)之间数据互通,于是出现了CGI(Common Gateway Interface)通用网关接口。简单理解,可以认为CGI是Web服务器和运行其上的应用程序进行“交流”的一种约定。
1.1 CGI原理
组成CGI通信系统的是两部分:一部分是html页面,就是在用户端浏览器上显示的页面。另一部分则是运行在服务器上的Cgi程序。它们之间的通讯方式如下图:
CGI通信中会包含两种通信协议:
- 服务器和客户端之间的HTTP通信,即客户端的浏览器和服务器端的HTTP服务器之间的HTTP通信。
- 服务器和CGI程序之间是通过标准输入输出来进行数据传递。
CGI则是Web服务器和一个独立的进程之间的协议,它肩负着网关Gateway的职责,把HTTP请求Request的Header头设置成进程的环境变量,HTTP请求的Body正文设置成进程的标准输入,进程的标准输出设置为HTTP响应Response,包含Header头和Body正文。
简单来说,CGI实际上是一个接口标准Interface。而通常所说的CGI指代其实是CGI程序,也就是实现了CGI接口标准的程序,只要编程语言具有标准输入、标准输出和环境变量,就可以用来编写CGI程序。
1.2 输入/出
Web服务器在接收到用户浏览器的HTTP请求,比如请求如下URL:
http://www.xxxx.me/cgi-bin/helloworld.cgi
此时在Web服务器调用helloworld.cgi
之前,会把各类HTTP请求中的信息以环境变量
的方式写入OS。CGI程序本质是OS上一个普通的可执行程序,它通过语言本身库函数来获取环境变量
,从而获得数据输入。
除环境变量
外,另外一个CGI程序获取数据的方式就是标准输入(stdin)。如post请求一个CGI的URL,那么POST的数据,CGI是通过标准输入来获取到的。
CGI输出HTML页面,则是向标准输出去写入数据即可。比如printf
、cout
,比如System.out.println
,又比如print
、echo
等。因为Web服务器已经做了重定向,将标准输出重定向给Web服务器的与浏览器连接的socket。CGI的输出承担的是HTTP协议的响应部分,因此除了正常的HTML页面内容,HTTP响应报头也要标准输出出来。
cout<<"Content-Type:text/html\n\n"<<endl;
1.3 环境变量
CGI程序通过标准输入(STDIN)和标准输出(STDOUT)进行数据的输入输出,此外CGI程序还通过环境变量来得到输入,操作系统提供了许多环境变量,它们定义了程序的执行环境,应用程序可以存取它们。Web服务器和CGI接口又另外设置了一些环境变量,用来向CGI程序传递一些重要的参数。
对于CGI程序来说,它继承了系统的环境变量。CGI环境变量在CGI程序启动时初始化,在结束时销毁。
当一个CGI程序不是被HTTP服务器调用时,它的环境变量几乎是系统环境变量的复制。当这个CGI程序被HTTP服务器调用时,它的环境变量就会多了以下关于HTTP服务器、客户端、CGI传输过程等项目。
环境变量是一个保存用户信息的内存区。当客户端的用户通过浏览器发出CGI请求时,服务器就寻找本地的相应CGI程序并执行它。在执行CGI程序的同时,服务器把该用户的信息保存到环境变量里。接下来,CGI程序的执行流程是这样的:查询与该CGI程序进程相应的环境变量:第一步是request_method
,如果是POST,就从环境变量的len
,然后到该进程相应的标准输入取出len
长的数据。如果是GET,则用户数据就在环境变量的QUERY_STRING
里。
1.3.1 与请求相关的环境变量
变量名称 | 说明 |
---|
REQUEST_METHOD | 服务器与CGI程序之间的信息传输方式。一般包括两种:POST 和GET ,其他方式也要考虑。 |
QUERY_STRING | 采用GET时所传输的信息,GET方法还通过此变量向CGI程序传递Form表单中的数据 |
CONTENT_LENGTH | STDIO中的有效信息长度 |
CONTENT_TYPE | 指示所传来的信息的MIME类型,例如application/x-www-form-urlencoded ,表示数据来自HTML表单,并且经过了URL编码。 |
CONTENT_FILE | 使用Windows HTTPd/WinCGI标准时,用来传送数据的文件名 |
PATH_INFO | 路径信息 |
PATH_TRANSLATED | CGI程序的完整路径名 |
SCRIPT_NAME | 所调用的CGI程序的名字 |
1.3.2 与服务器相关的环境变量
变量名称 | 说明 |
---|
GATEWAY_INTERFACE | 服务器所实现的CGI版本 |
SERVER_NAME | 服务器的IP或名字 |
SERVER_PORT | 主机的端口号 |
SERVER_SOFTWARE | 调用CGI程序的HTTP服务器的名称和版本号 |
1.3.3 与客户端相关的环境变量
变量名称 | 说明 |
---|
REMOTE_ADDR | 客户机的主机名 |
REMOTE_HOST | 客户机的IP地址 |
ACCEPT | 列出能被次请求接受的应答方式。客户机所支持的MIME类型清单,内容如:image/gif,image/jpeg |
ACCEPT_ENCODING | 列出客户机支持的编码方式 |
AUTORIZATION | 表明被证实了的用户 |
FORM | 列出客户机的EMAIL地址 |
IF_MODIFIED_SINGCE | 当用GET方式请求并且只有当文档比指定日期更早时才返回数据 |
PRAGMA | 设定将来要用到的服务器代码 |
REFFERER | 指出连接到当前文档的文档URL |
USER_AGENT | 客户端浏览器的消息 |
1.3.4 详细说明
1.3.4.1 REQUEST_METHOD
它的值一般包括两种:POST
和GET
,但编写CGI程序时,最后还要考虑其他的情况。
1.POST方法
如果采用POST方法,那么客户端来的用户数据将存放在CGI进程的标准输入中,同时将用户数据的长度赋予环境变量中的CONTENT_LENGTH
。客户端用POST方式发送数据有一个相应的MIME类型(通用Internet邮件扩充服务:Multi-purpose Internet Mail Extensions)。目前,MIME类型一般是:application/x-wwww-form-urlencoded
,该类型表示数据来自HTML表单。该类型记录在环境变量CONTENT_TYPE中,CGI程序应该检查该变量的值。
2.GET方法
在该方法下,CGI程序无法直接从服务器的标准输入中获取数据,因为服务器把它从标准输入接收到得数据编码到环境变量QUERY_STRING
(或PATH_INFO
)。
3.POST与GET的区别:
以GET方式接收的数据是有长度限制,而用POST方式接收的数据是没有长度限制的。并且,以GET方式发送数据,可以通过URL的形式来发送,但POST方式发送的数据必须要通过 Form 才到发送。
1.4 各种语言的封装
1.4.1 C语言
C语言中如果想要实现CGI编程,一般均自行开发,可以参考以下文章,这里不做赘述。
c语言实现cgi
1.4.2 C++
C++开发CGI可以使用GUN的一个开源库 Cgicc。可以满足常用的各类需求,除了解析get/post请求外,还能重定向,还可以设置Cookie,还可以上传文件等等等等。
美中不足的就是Cgicc库不支持SESSION。但是这个问题不大,我们可以很容易使用Cookie来实现SESSION功能。由于CGI本身是请求一次就创建一个进程,返回之后进程就结束(下文的FastCGI除外)。这时要在服务端维持一个SESSION的变量可选的解决方案是:用文件存储或者在Redis、Memcached等内存数据库中存储。而发给客户端的SESSIONID就用Cgicc已经支持的Cookie功能来完成,就可以了。
以下网络中的一个demo示例:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int get_inputs()
{
int length;
char *method;
char *inputstring;
method = getenv("REQUEST_METHOD");
if(method == NULL)
return 1;
if(!strcmp(method, "POST"))
{
length = atoi(getenv("CONTENT_LENGTH"));
if(length != 0)
{
inputstring = malloc(sizeof(char)*length + 1)
fread(inputstring, sizeof(char), length, stdin);
}
}
else if(!strcmp(method, "GET"))
{
Inputstring = getenv("QUERY_STRING");
length = strlen(inputstring);
}
if(length == 0)
return 0;
}
1.4.3 PHP
PHP-CGI是PHP自带的FastCGI管理器。
而PHP-FPM(FastCGI Process Manager),它是比旧的FastCGI更好的FastCGI实现。它作为独立的 FastCGI 服务器运行。一般来说,它是Web服务器(Apache,Nginx…)的PHP接口,允许Web Server与PHP交互。
1.4.4 Perl
这里也是给出一个网络上的demo示例:
$method = $ENV{'REQUEST_METHOD'};
if($method eq 'POST')
{
Read(STDIN, $input, $ENV{'CONTENT_LENGTH'});
}
if($method eq 'GET' || $method eq 'HEAD')
{
$input = $ENV{'QUERY_STRING'};
}
if($input eq "")
{
&print_form;
exit;
}
1.4.5 Python
Python代码实现更简单,cgi.FieldStorage()
返回一个字典,字典的每一个key就是变量名,key对应的值就是变量名的值,更本无需用户再去进行数据解码!
获取环境变量的时候,如果先判断REQUEST_METHOD
是否存在,程序会更健壮,否则在某些情况下可能会造成程序崩溃。因为假若CGI程序不是由服务器调用的,那么环境变量集里就没有与CGI相关的环境变量(如REQUEST_METHOD
,REMOTE_ADDR
等)添加进来,也就是说getenv("REQUEST_METHOD")
将返回NULL!
#!/usr/local/bin/python
import cgi
def main():
form = cgi.FieldStorage()
1.5 现代CGI的编程范式
CGI可以直接返回一个html网页。CGI程序本身也可以进行各种计算、逻辑处理任务。随着各类web前后端技术的发展,以及大数据、高并发的Server使用场景越来越多。现代的CGI的用法,在发生变化。
现在,越来越多的任务从后端转移到前端,前端页面利用丰富的Js技术来进行更多的处理。
- JS可以使用Ajax技术来向后台CGI发起数据请求。Ajax完成的是不需要刷新整个页面就可以加载后端数据(比如从数据库中取出)。
- CGI一般不再用于直接返回html页面,同时将复杂的计算、IO任务下沉到后端(后端可以进一步进行路由转发,实现负载均衡)。使CGI作为前后端之间的中间层。彼时CGI的职能是完成基本的数据交换:解析前端数据请求,再转发给对应后端;然后从后端取回数据,给前端返回XML或JSON。
- 前端JS利用XML/JSON中的数据来进行填充,绘制出丰富的页面。
1.6 缺点
CGI有一大硬伤。每次HTTP请求CGI,Web服务器都有启动一个新的进程去执行这个CGI程序,即颇具Unix特色的fork-and-execute。当用户请求量大的时候,这个fork-and-execute的操作会严重拖慢Web服务器的性能。
时势造英雄,FastCGI(简称FCGI)技术应运而生。简单来说,其本质就是一个常驻内存的进程池技术,由调度器负责将传递过来的CGI请求发送给处理CGI的handler进程来处理。在一个请求处理完成之后,该处理进程不销毁,继续等待下一个请求的到来。
当然FCGI其实也并不是什么惊世骇俗的创意,很容易联想到的解决思路。资源池是后台性能优化中的常见套路。Java发明的Servlet技术也是一种常驻内存的网关通信技术,只不过它采用的是多线程而非进程。
2 FastCGI
FastCGI是Web服务器与处理程序之间通信的一种协议,是CGI的改进版本。由于CGI程序反复加载CGI而造成性能低下,如果CGI程序保持在内存中并接收FastCGI进程管理器调度,则可以提供良好的性能、伸缩性、Fail-Over特性等。
FastCGI就是常驻型的CGI,可以一直运行。在请求到达时不会耗费时间去Fork创建一个进程来处理。FastCGI是语言无关的、可伸缩架构的CGI开放扩展,它将CGI解释器进程保持在内存中,因此获得较高的性能。
2.1 FastCGI的工作流程
- Web服务器启动时载入FastCGI进程管理,如IIS的ISAPI、Apache的Module…
- FastCGI进程管理器自身初始化,并启动多个CGI解释器进程php-cgi并等待Web服务器的连接。
- 当客户端请求到达Web服务器时,FastCGI进程管理器选择并连接一个CGI解释器,Web服务器将CGI环境变量和标准输入发送到FastCGI子进程PHP-CGI。
- FastCGI子进程完成处理后将标准输出和错误信息,从同一连接返回给Web服务器。当FastCGI子进程关闭连接时请求便处理完毕。FastCGI子进程接着等待并处理来自FastCGI进程管理器(运行在Web服务器中)的下一个连接。在CGI模式中,PHP-CGI在此便退出了。
针对CGI中Web Server和CGI程序必须在同一个主机上的问题,FastCGI也进行了改进,允许FastCGI程序运行在不同的主机之上,而Web Server和FastCGI之间通过TCP连接进行通信,这种方式可以使Web Server和FastCGI程序独立配置和启动,也可以通过负载均衡提高系统的伸缩性和扩展性,当然两者仍然也可以同时部署在同一个主机上。此点可以参考nginx中配置的FastCGI程序的运行:
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
# location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
# }
3 Servlet
Servlet就是Java语言为了在Web中生成动态内容的定义的用于开发Web应用程序的基础技术标准,实现这个标准接口的类称为Servlet。它和CGI一样,都是Web低层通信的标准。
与CGI不同,CGI对每个Http请求生成一个CGI请求,然后将针对CGI请求生成一个单独的进程。Servlet运行在一个进程中,对于每个请求则会生成一个单独的线程,由这些独立的线程处理对应的http请求,Servlet执行完毕后不会销毁,而是驻留在内存中直到Servlet Container关闭,以便能随时处理http请求。
还有个不同点就是CGI程序是直接面向OS,而不像Servlet面向的是JVM虚拟机,所以Servlet的移植性更好。
一个Servlet应用程序经常包括一个或者多个Servlet,Servlet是运行在Servlet容器中的,它不能自动运行。
目前最新的Servlet标准应该是4.0。
Servlet在1999年所发布的J2SE1.2版本中首次面世,Servlet在JavaWEB的开发中发挥着重要的作用。JavaEE8对Servlet4.0进行了重要的更新。其中服务器推送是最主要的更新,如果要使用服务器推送的功能,则我们必须使用HTTP/2.0版本的协议。JavaEE8提供了对Servlet映射的运行时发现,在运行时我们可以获取Servlet的名称,Servlet的映射路径。JavaEE8简化了对Filter的开发。
3.1 Servlet有用的资源
- Servlet 有用的资源
- Java Servlet4.0
- Java Servlet3.1
- Servlet规范_一文概括所有
3.2 Servlet容器
在JavaWeb中,Web容器和Servlet容器的概念容易混淆。Servlet容器是Web服务器或应用程序服务器的一部分,它提供网络服务,通过这些服务发送请求和响应,解码基于MIME的请求,并格式化基于MIME的响应。Servlet容器还包含并管理Servlet的整个生命周期。
Servlet/JSP容器已经很成熟,并且被广泛的独立部署。Apache Tomcat和Jetty作为最流行的Servlet/JSP容器。
像Tomcat就是一个免费的开放源代码的Web应用服务器,属于轻量级应用服务器,在中小型系统和并发访问用户不是很多的场合下被普遍使用,具有处理HTML页面的功能。另外它还是一个Servlet和JSP容器,独立的Servlet容器是Tomcat的默认模式,这样就兼具了处理JSP的功能。
4 WSGI
WSGI,全称Python Web Server Gateway Interface,是Python语言中类似与CGI的标准。指定了Web服务器和PythonWeb应用或Web框架之间的标准接口,以提高Web应用在一系列Web服务器间的移植性。官方文档
通俗的讲,WSGI规范了一种简单的接口,解耦了Web服务器和PythonWeb应用程序,使得双边的开发者更加专注自身特性的开发。
WSGI是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,以提升可移植Web应用开发的共同点。WSGI是基于现存的CGI标准而设计的。WSGI就是Python的CGI包装,相对于Fastcgi是PHP的CGI包装。
4.1 WSGI构成
WSGI将web组件分为三类: web服务器,web中间件,web应用程序, wsgi基本处理模式为 : WSGI Server -> (WSGI Middleware)* -> WSGI Application
WSGI协议其实是定义了一种server与application解耦的规范,即可以有多个实现WSGI server的服务器,也可以有多个实现WSGI application的框架,那么就可以选择任意的server和application组合实现自己的web应用。例如uWSGI和Gunicorn都是实现了WSGI server协议的服务器,Django,Flask是实现了WSGI application协议的web框架,可以根据项目实际情况搭配使用。
4.1.1 WSGI Server/gateway
WsgiServer可以理解为一个符合WSGI规范的WebServer,负责接收request请求和提供并发,封装一系列环境变量,按照WSGI规范调用注册的Wsgi Application并将request转发给application,最后将Application返回的response返回给客户端。
- 接收 HTTP 请求,但是不关心 HTTP url, HTTP method 等
- 为 environ 提供必要的参数,实现一个回调函数 start_response,并传给 callable object
- 调用 callable object
以Python自带的wsgiref为例,wsgiref是按照WSGI规范实现的一个简单WSGI Server。
- 服务器创建socket,监听端口,等待客户端连接。
- 当有请求来时,服务器解析客户端信息放到环境变量environ中,并调用绑定的handler来处理请求。
- handler解析这个http请求,将请求信息例如method,path等放到environ中。
- wsgi handler再将一些服务器端信息也放到environ中,最后服务器信息,客户端信息,本次请求信息全部都保存到了环境变量environ中。
- wsgi handler 调用注册的wsgi app,并将environ和回调函数传给wsgi app
- wsgi app 将reponse header/status/body 回传给wsgi handler
- 最终handler还是通过socket将response信息塞回给客户端。
直接使用支持 WSGI 框架的 wsgiref 库,编写一个样例:
# server/gateway side
if __name__ == '__main__':
from wsgiref.simple_server import make_server
server = make_server('0.0.0.0', 8080, application)
server.serve_forever()
4.1.2 WSGI Application
WSGI Application接收由server转发的request,处理请求,并将处理结果返回给server。其实就是一个普通的callable对象,当有请求到来时,wsgi server会调用这个wsgi application。
Callable对象可以是以下三者之一:
function, method
class
instance with a __call__ method
Callable对象必须满足以下两个条件:
- 接受两个参数:
- 字典(
environ
),可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。 - 回调函数(
start_response
),是一个callback函数,wsgi application通过调用`start_response·将response headers/Http status 返回给wsgi server。
- 返回一个可迭代的值:wsgi app会return一个iterator对象 ,这个iterator就是response body。
基于callable function的application/framework样例:
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['This is a python application!']
基于callable class的application/framework样例:
class ApplicationClass(object):
def __init__(self, environ, start_response):
self.environ = environ
self.start_response = start_response
def __iter__(self):
self.start_response('200 OK', [('Content-type', 'text/plain')])
yield "Hello world!n"
4.1.3 WSGI MiddleWare
application中可以包括多个栈式的中间件(middlewares),这些中间件需要同时实现server与application,因此可以在WSGI服务器与WSGI应用之间起调节作用:对服务器来说,中间件扮演应用程序,对应用程序来说,中间件扮演服务器。
Middleware处于server/gateway和application/framework之间,对server/gateway来说,它相当于 application/framework;对application/framework来说,它相当于server/gateway。每个middleware实现不同的功能,我们通常根据需求选择相应的middleware并组合起来,实现所需的功能。比如,可在middleware中实现以下功能:
- 根据 url 把用户请求调度到不同的 application 中。
- 负载均衡,转发用户请求
- 预处理 XSL 等相关数据
- 限制请求速率,设置白名单
本例实现了一个 IPBlacklist 的 middleware:
class IPBlacklistMiddleware(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
ip_addr = environ.get('HTTP_HOST').split(':')[0]
if ip_addr not in ('127.0.0.1'):
return forbidden(start_response)
return self.app(environ, start_response)
def forbidden(start_response):
start_response('403 Forbidden', [('Content-Type', 'text/plain')])
return ['Forbidden']
def application(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
return ['This is a python application!']
if __name__ == '__main__':
from wsgiref.simple_server import make_server
application = IPBlacklistMiddleware(application)
server = make_server('0.0.0.0', 8080, application)
server.serve_forever()
参考资料
- CGI是什么
- 网关协议CGI、FastCGI、WSGI的区别
- python WSGI框架详解
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)