摘要[TOC]


目录

  1. 常用web服务器
  2. 并发模型
  3. 协议进化史
  4. CGI协议
  5. Apache的mod_*模块
  6. FastCGI协议
  7. Python的WSGI标准
  8. Java的Servlet规范
  9. 写在最后
  10. 附录

常用web服务器[TOC]


并发模型(强烈建议阅读)[TOC]


协议进化史[TOC]

CGI -> Apache Mod_* -> FastCGI -> (Servlet、WSGI等)


CGI协议[TOC]

CGI是一个非常古老的协议,因为性能问题,现在基本没人使用CGI了。同时,出于安全性考虑,Nginx并不支持运行外部程序,所以Nignx原生是不支持CGI的,为了测试,我们先安装支持CGI的Apache(本文是在CentOS7下测试的):

sudo yum install -y httpd httpd-devel httpd-tools

Apache的默认配置文件在:/etc/httpd/conf/httpd.conf。下面看一些主要配置:

主服务器 配置:
接下来的这些指令用来设置主服务器所使用的配置,主服务器负责处理所有没有被任何虚拟主机处理的请求。同时,这些配置也负责为虚拟主机的配置提供默认值。
这些指令也可以出现在<VirtualHost>标签中,此时,它们会覆盖从主服务器继承来的默认值。

# Customizable error responses come in three flavors:
# 1) plain text 2) local redirects 3) external redirects
#
# Some examples:
#ErrorDocument 500 "The server made a boo boo."
#ErrorDocument 404 /missing.html
#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
#ErrorDocument 402 http://www.example.com/subscription_info.html

下面我们先看一个CGI的例子:

# /etc/httpd/conf/httpd.conf

ServerRoot "/etc/httpd"
Listen 8081
User apache
Group apache

ServerAdmin jordan23nbastar@yeah.net
ServerName 47.52.166.98:8081

Include conf.modules.d/*.conf

<IfModule alias_module>
    ScriptAlias /cgi-bin/ "/var/www/cgi-bin/"
</IfModule>

ErrorLog "logs/error_log"
LogLevel warn
<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    CustomLog "logs/access_log" combined
</IfModule>

<IfModule mime_magic_module>
    MIMEMagicFile conf/magic
</IfModule>

<IfModule mime_module>
    TypesConfig /etc/mime.types
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
    AddType text/html .shtml
    AddOutputFilter INCLUDES .shtml
</IfModule>
#! /usr/bin/env python

import os
import sys 
import json

print "Content-Type: application/json"
print

d = {}
d["pid"] = os.getpid()
d["command_line_args"] = sys.argv[1:]
d["stdin"] = sys.stdin.read()
d["environment"] = dict([(str(k), str(v))
    for k, v in os.environ.iteritems()])

print json.dumps(d, indent=2)
chmod a+x /var/www/cgi-bin/test.py
sudo systemctl restart httpd.service
[root@iZj6chejzrsqpclb7miryaZ nginx]# curl http://timd.cn:8081/cgi-bin/test.py?is_index
{
  "environment": {
    "CONTEXT_DOCUMENT_ROOT": "/var/www/cgi-bin/", 
    "SERVER_SOFTWARE": "Apache/2.4.6 (CentOS)", 
    "CONTEXT_PREFIX": "/cgi-bin/", 
    "SERVER_SIGNATURE": "", 
    "REQUEST_METHOD": "GET", 
    "SERVER_PROTOCOL": "HTTP/1.1", 
    "QUERY_STRING": "is_index", 
    "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", 
    "HTTP_USER_AGENT": "curl/7.29.0", 
    "SERVER_NAME": "timd.cn", 
    "REMOTE_PORT": "47284", 
    "SERVER_PORT": "8081", 
    "SERVER_ADDR": "172.31.238.196", 
    "DOCUMENT_ROOT": "/etc/httpd/htdocs", 
    "SCRIPT_FILENAME": "/var/www/cgi-bin/test.py", 
    "SERVER_ADMIN": "jordan23nbastar@yeah.net", 
    "HTTP_HOST": "timd.cn:8081", 
    "SCRIPT_NAME": "/cgi-bin/test.py", 
    "REQUEST_URI": "/cgi-bin/test.py?is_index", 
    "HTTP_ACCEPT": "*/*", 
    "GATEWAY_INTERFACE": "CGI/1.1", 
    "REMOTE_ADDR": "47.52.166.98", 
    "REQUEST_SCHEME": "http", 
    "UNIQUE_ID": "Wk7v0hrJnREyoIubeYkhzAAAAAA"
  }, 
  "stdin": "", 
  "pid": 21243, 
  "command_line_args": [
    "is_index"
  ]
}
[root@iZj6chejzrsqpclb7miryaZ nginx]# curl http://timd.cn:8081/cgi-bin/test.py?is_index
{
  "environment": {
    "CONTEXT_DOCUMENT_ROOT": "/var/www/cgi-bin/", 
    "SERVER_SOFTWARE": "Apache/2.4.6 (CentOS)", 
    "CONTEXT_PREFIX": "/cgi-bin/", 
    "SERVER_SIGNATURE": "", 
    "REQUEST_METHOD": "GET", 
    "SERVER_PROTOCOL": "HTTP/1.1", 
    "QUERY_STRING": "is_index", 
    "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin", 
    "HTTP_USER_AGENT": "curl/7.29.0", 
    "SERVER_NAME": "timd.cn", 
    "REMOTE_PORT": "47286", 
    "SERVER_PORT": "8081", 
    "SERVER_ADDR": "172.31.238.196", 
    "DOCUMENT_ROOT": "/etc/httpd/htdocs", 
    "SCRIPT_FILENAME": "/var/www/cgi-bin/test.py", 
    "SERVER_ADMIN": "jordan23nbastar@yeah.net", 
    "HTTP_HOST": "timd.cn:8081", 
    "SCRIPT_NAME": "/cgi-bin/test.py", 
    "REQUEST_URI": "/cgi-bin/test.py?is_index", 
    "HTTP_ACCEPT": "*/*", 
    "GATEWAY_INTERFACE": "CGI/1.1", 
    "REMOTE_ADDR": "47.52.166.98", 
    "REQUEST_SCHEME": "http", 
    "UNIQUE_ID": "Wk7v2NJfx11vvtodsSxwwwAAAAU"
  }, 
  "stdin": "", 
  "pid": 21246, 
  "command_line_args": [
    "is_index"
  ]
}

我们可以看到两次的pid是不一样的
下面简单介绍一下CGI的整个执行流程:

综上,可以看出,CGI的执行模式是:fork-execute-destroy,这正是其性能低下的原因。

当使用Python开发CGI程序的时候,可以使用wsgiref模块


Apache的mod_*模块[TOC]

Apache mod_*模块的本质就是将各种脚本语言(比如PHP、Perl、Python等)的解释器内嵌到Apache中,这样会获得两方面的好处:

但是,随之也引来了坏处:

非常值得说明的是:Openresty也是将Lua解释器或LuaJIT解释器以模块的方式嵌入到Nginx中,进而使得Nginx有运行Lua脚本的能力。并且,Nginx官方最近推出了nginScript,其本质是将(他们自研的)JS解释器嵌入到Nginx,它通过融入JavaScript代码,对NGINX的配置语法进行扩展,以便实现复杂的配置。

接下来,我们以Apache的mod_wsgi为例,进行一下实践。mod_wsgi模块可以支持任何实现WSGI标准的Python应用程序。

1,安装mod_wsgi
可以参考PYPI的说明。
鄙人是通过pip install mod_wsgi安装的,然后将/path/to/site-packages/mod_wsgi/server/mod_wsgi-py27.so拷贝到/etc/httpd/modules下。
2,编写WSGI应用程序

# /etc/httpd/wsgi.py

import os
import threading

counter = 0 

def application(environment, start_response):
    global counter
    counter = counter + 1 

    response = "pid is: %d, thread is: %d, counter is: %d" % ( 
        os.getpid(), threading.currentThread().ident, counter)
    length = len(response)
    writer = start_response("200 OK", [("Content-Length", str(length))])
    return [response]

3,配置Apache
本例中使用的mpm模式是worker模式:

# /etc/httpd/conf/httpd.conf

ServerRoot "/etc/httpd"
Listen 8081
User apache
Group apache

ServerAdmin jordan23nbastar@yeah.net
ServerName 47.52.166.98:8081

Include conf.modules.d/*.conf


LoadModule wsgi_module modules/mod_wsgi.so
<IfModule wsgi_module>
    WSGIScriptAlias /wsgi /etc/httpd/wsgi.py
    WSGIPythonPath /etc/httpd/
</IfModule>
<Files /etc/httpd/wsgi.py>
    Require all granted
</Files>

<IfModule mpm_worker_module>
    ServerLimit     1
    StartServers    1
    MaxSpareThreads 20
    MinSpareThreads 15
    ThreadsPerChild 25
</IfModule>


ErrorLog "logs/error_log"
LogLevel info
<IfModule log_config_module>
    LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
    CustomLog "logs/access_log" combined
</IfModule>

<IfModule mime_magic_module>
    MIMEMagicFile conf/magic
</IfModule>

<IfModule mime_module>
    TypesConfig /etc/mime.types
    AddType application/x-compress .Z
    AddType application/x-gzip .gz .tgz
    AddType text/html .shtml
    AddOutputFilter INCLUDES .shtml
</IfModule>

4,重启Apache,并测试一下

[root@iZj6chejzrsqpclb7miryaZ httpd]# systemctl restart httpd

[root@iZj6chejzrsqpclb7miryaZ httpd]# curl http://127.0.0.1:8081/wsgi
pid is: 14938, thread is: 140189502514944, counter is: 1

上面的例子中,使用的是mod_wsgi的嵌入模式,用到的指令包括:

1,WSGIScriptAlias URL-path file-path|directory-path [ options ]
与Alias指令类似,都是将URL映射到本地文件系统的特定区域。当第二个参数是目录的时候,那么会把它当作包含WSGI脚本的目录;当第二个参数是文件的时候,那么会把它当作WSGI脚本(它会被mod_wsgi的wsgi-script handler处理)。
比如:

WSGIScriptAlias /wsgi-scripts/ /web/wsgi-scripts/

那么,请求http://www.example.com/wsgi-scripts/name 会导致 服务器运行 /web/wsgi-scripts/name应用程序。
再比如:

WSGIScriptAlias /name /web/wsgi-scripts/name

那么,请求http://www.example.com/name 会导致 服务器运行 /web/wsgi-scripts/name应用程序。

2,WSGIPythonPath directory|directory-1:directory-2:…
用于添加Python的模块搜索路径,多个路径之间用:分隔(windows上用;分隔)。
除了嵌入模式,mod_wsgi还支持后台模式(非常值得动手实践一下)。更多关于mod_wsgi的配置和运行模式的资料,请参考:
参考文档8
参考文档9


FastCGI协议[TOC]

PHP目前最常用的部署方式就是Nginx + PHP-FPM。FPM的全称是FastCGI Process Manager。故名思意,PHP-FPM就是PHP-CGI进程的管理器。
前面已经提及,CGI性能低的原因是:每次请求都要Fork-Execute-Destroy。而FastCGI进程是常驻(long-live)的,一旦启动,它会一直存在,不必每次请求都fork一次。

一般情况下,FastCGI的完整执行流程是:

值得额外说明的是:在Web服务器的配置中,需要正确的设置FastCGI参数 SCRIPT_FILENAME ,否则会导致“Primary script unknown”之类的错误,该代表CGI脚本的完整路径。

接下来看一个Nginx + spawn-fcgi + Python flup的例子:

server {
    listen 8081;
    server_name test.timd.cn;

    location / { 
        fastcgi_pass 127.0.0.1:9091;
        include fastcgi_params;
    }   
}
#! /usr/bin/env python

from flup.server.fcgi import WSGIServer

def application(enviroment, start_response):
    response = "just test for fastcgi"
    length = len(response)
    start_response("200 OK", [
            ("Content-Length", str(length))
        ])  
    return [response]

if __name__ == "__main__":
    WSGIServer(application).run()
chmod a+x fastcgi.py
spawn-fcgi -f ./fastcgi.py -a 0.0.0.0 -p 9091 -F 4 -n

-F:用来指定子进程数
-f:用来指定fastcgi应用程序的文件名(文件必须是可执行的)
-a:用来指定绑定的IP地址
-p:用来指定要监听的端口
-n:表示前台运行

[root@iZj6chejzrsqpclb7miryaZ ~]# curl http://127.0.0.1:8081
just test for fastcgi

spawn-fcgi是从lighttpd脱离出来的,其一个不能容忍的缺点是:当它fork出的FastCGI进程被杀死后,它不会重启一个新的。


Python的WSGI标准[TOC]

PEP 3333详尽的描述了WSGI标准。感兴趣的童鞋,可以点击链接阅读英文原稿。不爱阅读英文文档的盆友,也可以看下这篇比较不错的中文文档

=====下面开始我的表演=====
WSGI的全称是Web Server Gateway Interface。它是一个描述:

的标准。
因为HTTP只能处理字节,所以WSGI应用程序返回的响应必须是字节,不能是unicode。在Python中,字节是指str(In Python2X)或bytes(In Python3X)。

WSGI包含三面的标准:

1,WSGI应用程序

2,WSGI服务器

3,WSGI中间件

对于WSGI服务器来说,它相当于WSGI应用程序;对于WSGI应用程序来说,它相当于WSGI服务器。

4,注意事项

5,例子

# test_wsgi.py
from wsgiref.simple_server import make_server

def application(environment, start_response):
    response = "just for test wsgi"
    start_response("200 OK", [])
    
    error = environment.get("wsgi.errors")
    error.write("test for error\n")

    for i in range(5):
        yield response + "\n"

class Middleware:
    def __init__(self, app):
        self._app = app 

    def __call__(self, environment, start_response):
        if environment.get("PATH_INFO") == "/test":
            start_response("200 OK", []) 
            return ["this is test\n"]
        return self._app(environment, start_response)

if __name__ == "__main__":
    httpd = make_server("0.0.0.0", 8081, Middleware(application))
    httpd.serve_forever()

uWSGI是一个既支持uwsgi协议,又支持http协议的WSGI服务器,它使用C编写的,性能非常好,功能也很全面。Gunicorn是一个用Python编写的WSGI服务器,它只支持http协议。在Python世界中,基本只在两者之间进行选择。


Java的Servlet规范[TOC]

Servlet是Java程序和Servlet引擎交互的接口规范。该规范用于:

这个是在gitbook上找到的一篇Servlet 3.1规范的中文翻译。PDF版本,可以点此下载。


写在最后[TOC]

鄙人就职于新浪和高德期间,主要负责一些业务的API开发,尤其是在新浪期间,接触到各种web部署方式,下面以新浪期间的一些经历,进行一下举例说明:


参考文档[TOC]