Web服务器相关的一些概念
前言
记录一下很常见,但也很容易忽略的一些问题。主要是跟 Web 服务器相关的。
本文涉及到的名词有:
cgi、fastcgi、wsgi、uwsgi、gunicore、nginx、httpd
Web 服务器
WSGI 服务
Web 框架
Web服务演进
基本架构:
1 | Client(用户) -> Web服务器 -> 网页源码 |
支持最基本的文件访问
1
2
3"""
Client -> Web服务器(apache) -> 静态文件
"""实现动态生成文件功能
1
2
3
4"""
Client -> Web服务器(apache) -> 静态文件
| -> cgi -> 动态生成文件
"""提升 cgi 效率
1
2
3
4"""
Client -> Web服务器(apache) -> 静态文件
| -> fastcgi -> 动态生成文件
"""提升静态文件效率效率
1
2
3
4"""
Client -> Web服务器(nginx / apache) -> 静态文件
| -> fastcgi -> 动态生成文件
"""某些语言的特定实现方式
1
2
3
4
5
6
7
8"""
Client -> Web服务器(nginx / apache) -> 静态文件
| -> uwsgi -> flask、django -> 动态生成文件
"""
"""
Client -> Web服务器(nginx / apache) -> 静态文件
| -> php-fpm(fastcgi) -> php文件 -> 动态生成文件
"""
通用规范
CGI
即 Common Gateway Interface(通用网关接口) 见
为什么要有 CGI?
早期的 Web 服务器仅能访问静态网页。你所访问的所有网页内容都是提前生成好放到服务器的上的,所谓的网页访问就是读取服务器上的某个文件然后返回内容。
但CGI 使动态网页成为可能。
CGI 如何实现的?
通过定义一系列的通信规范来实现。
简而言之就是将 url、header、params 放入环境变量里,当你访问一个 CGI 文件时,Web 服务器会执行此 CGI 文件(脚本)获取输出然后返回给浏览器。你可以在 CGI 脚本中返回任意内容。
CGI 程序可以用任何脚本语言或者编程语言实现,只要该语言可以在系统上运行。
例如:http://www.example.com/wiki.cgi
以前比较常用的是 Perl 来实现 CGI 脚本。
CGI环境变量示例:https://www.runoob.com/python/python-cgi.html
CONTENT_TYPE | 这个环境变量的值指示所传递来的信息的MIME类型。目前,环境变量CONTENT_TYPE一般都是:application/x-www-form-urlencoded,他表示数据来自于HTML表单。 |
---|---|
CONTENT_LENGTH | 如果服务器与CGI程序信息的传递方式是POST,这个环境变量即使从标准输入STDIN中可以读到的有效数据的字节数。这个环境变量在读取所输入的数据时必须使用。 |
HTTP_COOKIE | 客户机内的 COOKIE 内容。 |
HTTP_USER_AGENT | 提供包含了版本数或其他专有数据的客户浏览器信息。 |
PATH_INFO | 这个环境变量的值表示紧接在CGI程序名之后的其他路径信息。它常常作为CGI程序的参数出现。 |
QUERY_STRING | 如果服务器与CGI程序信息的传递方式是GET,这个环境变量的值即使所传递的信息。这个信息经常跟在CGI程序名的后面,两者中间用一个问号’?’分隔。 |
REMOTE_ADDR | 这个环境变量的值是发送请求的客户机的IP地址,例如上面的192.168.1.67。这个值总是存在的。而且它是Web客户机需要提供给Web服务器的唯一标识,可以在CGI程序中用它来区分不同的Web客户机。 |
REMOTE_HOST | 这个环境变量的值包含发送CGI请求的客户机的主机名。如果不支持你想查询,则无需定义此环境变量。 |
REQUEST_METHOD | 提供脚本被调用的方法。对于使用 HTTP/1.0 协议的脚本,仅 GET 和 POST 有意义。 |
SCRIPT_FILENAME | CGI脚本的完整路径 |
SCRIPT_NAME | CGI脚本的的名称 |
SERVER_NAME | 这是你的 WEB 服务器的主机名、别名或IP地址。 |
SERVER_SOFTWARE | 这个环境变量的值包含了调用CGI程序的HTTP服务器的名称和版本号。例如,上面的值为Apache/2.2.14(Unix) |
CGI 的缺点?
一般每次的 CGI 请求都需要新生成一个程序的副本来运行,这样大的工作量会很快将服务器压垮。
因此一些更有效的技术像 mod_perl,可以让脚本解释器直接作为模块集成在 Web 服务器(例如:Apache)中,这样就能避免重复加载和初始化解释器。
不过这只是就那些需要解释器的高级语言(即解释语言,例如python)而言的,使用诸如C一类的编译语言则可以避免这种额外负荷。
由于C及其他编译语言的程序与解释语言程序相比,前者的运行速度更快、对操作系统的负荷更小,使用编译语言程序是可能达到更高执行效率的.
然而因为开发效率等原因,在目前解释型语言还是最合适的。
FastCGI
即 FastCommon Gateway Interface(快速通用网关接口)见:https://zh.wikipedia.org/wiki/FastCGI
为了解决 CGI 效率低下的问题而进行的改进。
CGI 会对每个请求创建一个进程,在请求结束时销毁。开销太大。
而 FastCGI 会使用进程池来处理请求,避免频繁开启进程的开销。
特定语言实现
WSGI
即 Python Web Server Gateway Interface(Python Web 服务器网关接口)
https://zh.wikipedia.org/wiki/Web%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3
https://www.liaoxuefeng.com/wiki/1016959663602400/1017805733037760
起初是为 Python 定义的 Web 服务器与 Web 应用程序之间的接口。后来一些其他语言也参考实现了自己的 wsgi
为什么要有WSGI?
抄自PEP-3333:
Python 当前拥有各种各样的 Web 应用程序框架,例如 Zope,Quixote,Webware,SkunkWeb,PSO 和 Twisted Web-仅举几例。对于Python的新用户来说,各种各样的选择可能是个问题,因为通常来说,他们对 Web 框架的选择将限制他们对可用 Web 服务器的选择,反之亦然。
相比之下,尽管 Java 具有许多可用的 Web 应用程序框架,但是Java的 Servlet API 使得使用任何 Java Web 应用程序框架编写的应用程序都可以在支持 Servlet API 的任何 Web 服务器中运行。
WSGI 规范的实现?
定义了两类对象:
Web 服务器:例如 uWSGI、gunicorn
Web 应用程序:Flask、Django 的 application
Web 应用程序只需要实现一个函数即可:
1 | def application(environ, start_response): |
Web 服务器收到请求后会调用 application 函数
wsgi 的 environment示例:
1 | { |
WSGI 的好处?
- 如果没有的话,Web 框架就只能通过 Socket 接收原始数据,自己实现 HTTP 协议的解析。但这些事情其实是不必要的,交给专业的人来做更好。
- 一般 WSGI 服务器都是 C 写的,效率更高。
- 对 Web 框架的使用者来说,他们并不关心如何接收 HTTP 请求,也不关心如何将请求路由到具体方法处理并将响应结果返回给用户。Web 框架的使用者在大部分情况下,只需要关心如何实现业务的逻辑。
Flask 干了啥?
flask.Flask 的
1 | def __call__(self, environ, start_response): |
Django干了啥?
1 | # django.core.handlers.wsgi.py |
uwsgi VS uWSGI
uwsgi 是一个 uWSGI 服务器的专有协议,参见
uWSGI 是一个Web 服务器,实现了 wsgi、uwsgi 两种协议
uWSGI 配置示例:
1 | [uwsgi] |
uWSGI 很复杂 有兴趣的可以研究一下
混淆点:
WSGI: Python Web Server Gateway Interface
uWSGI: 实现了 WSGI、uwsgi 两种协议的一个Web服务器
uwsgi: uWSGI 服务器自定义的一种协议
为啥我不用 uWSGI 也可以启动 Web 服务?
如果你看源码的话,会发现其实是启动了个 Python 内置的 WSGI 服务器。
gunicore
跟 uWSGI 同属实现了 WSGI 协议的 Web 服务器。前身是 ruby 的 unicore。
PHP
php 本质上是一个 CGI 脚本。所以 PHP 写的网站都是.php结尾的文件。
php-fpm 是一个实现了 fastcgi 规范的进程管理服务
Web服务器
Nginx
作用很多,不一一说明了。
1、静态文件访问功能(很优秀)
2、服务转发
3、负载均衡
4、虚拟主机:不同域名访问同一个IP、同一个主机上的不同服务
一般生产服务会使用 nginx + uwsgi + flask 部署,
静态文件请求使用 nginx 处理,动态请求直接转发到 uWSGI
那么为啥 nginx 不能直接接 flask?
- 因为 nginx 不会 python
- 如果在 nginx 中直接用 WSGI, 那么 nginx 线程中就要启动 python 解释器,效率太低。
Apache
感觉已经过时了,不过曾经很火,因为出现的比较早,所以还有很多老系统在用。
IIS
微软开发windows服务器上运行
其他语言
Java 现在流行内置 tomcat 了
php 最近也有一些内置 http server 的框架,性能远超跑 fpm 里的框架
ruby 版的 wsgi 叫 rack
go 语言则是标准库内置 http 服务
nodejs 也是内置 http
参考文档
https://www.python.org/dev/peps/pep-3333/