Web应用的整体架构

之前一直没有系统地学习一下python Web服务器的知识,今天就好好地学习了一下。

如图是一个Web的整体架构:

一、web Server

其实server可以分成两部分,一是WSGI服务器,二是http服务器。

1、Nginx

Nginx在静态资源处理上性能很好,加上了Nginx,就极大减轻了Django的负担。但Nginx绝不仅仅是做静态资源处理器。比如Nginx具有负载均衡功能,此外支持热部署,可以在运行的情况下进行软件升级。此外,它可以支持健康状态检测。即如果后端出现问题,它就不会网后端发送请求,直到后端应用恢复正常。最重要的就是nginx具有高并发的特性,可以缓冲请求,防止WSGI server垮掉。

Nginx的高并发来源于它是采用单线程来异步非阻塞处理请求(管理员可以配置Nginx主进程的工作进程的数量)(epoll),不会为每个请求分配cpu和内存资源,节省了大量资源,同时也减少了大量的CPU的上下文切换。他同样是采用pre-fork的模式。

我觉得让我感触最深的除了是静态资源的缓存等处理,还有就是可以帮助我拦截一些请求。比如我在建站的时候,发现每天都有一个user_agent是YiSouSPider的客户端访问我,而且访问量特别大,后来我查资料发现它是阿里的一个爬虫。我阻止这个请求的方法很简单:直接在nginx配置文件中配置,如果遇到该客户端就禁止访问,具体如下:

 if ($http_user_agent ~* "YisouSpider") {
        return 403;
    }

2、WSGI服务器—Gunicorn

WSGI服务器有多种类型,gunicorn就是其中一个,它负责根据请求调用Web应用。Web应用与服务器之间通过WSGI协议进行通信。

wsgi接口协议,使得我们不必处理底层的东西,如tcp连接、http请求和响应格式等,只需专注于做web业务相关的工作。 wsgi要求开发者自己定义一个http响应函数,用来响应http请求。函数如下:

def application(environ, start_response):
    start_response('200 ok', [('content-type', 'text/html')])
    return 'hello, web!'

environ包含所有http请求头信息,是一个字典dict,它和cgi环境变量差不多: path_info、request_method、server_name,query_string等等。 start_response是一个发送http响应的函数,它接收两个参数,一个是http响应码,另外一个是http header,它是一个列表list。列表中的元素是一个含有两个str的元祖,这两个字符串其实就是一个是key,一个是value。形式如上面函数。

执行完start_response后,服务器会返回把body,即'hello, web!'给客户端。

http请求的所有输入信息都可以通过environ获得,http响应的输出都可以通过start_response()加上函数返回值作为body。
整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。
不过,这个application()函数如何进行调用?如果我们自己调用,两个参数environ和start_response我们没法提供,返回的str也没法发给浏览器。所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,uwsgi,Gunicorn等等,FLask,Django都内置了WSGI服务器。

简单调用过程:uWSGI会把接收到的请求按照指定协议解析,然后把解析的结果(譬如:HTTP各请求头数据)设置到environ变量中,接着按照WSGI规范回调web app(uWSGI默认回调application函数,并且传递environ和start_response两个参数),最终web app开始处理请求,并把结果返回给uWSGI。

WSGI服务器会找到app中的application函数(应用服务器使用该函数来与你的代码进行交互),举例: 在Django中, WSGI 服务器从它们的配置中获得application 可调用对象的路径。Django 内建的服务器,叫做runserver 和runfcgi 命令,是从WSGI_APPLICATION 设置中读取它。默认情况下,它设置为 .wsgi.application,指向 /wsgi.py 中的application 可调用对象。

gunicorn采用了pre-fork的多进程模式,并可采用gevent等处理server。每一个进程创建一个应用,一个应用下可接收多个request进行处理,gevent会在协程池中调用协程来处理请求。

更多关于gunicorn的详细介绍,请访问我的文章: Gunicorn

二、Web应用

虽然wsgi很方便,但是对于要开发复杂的web程序来说是很复杂的,因为一个web要包含n多的url,针对于每一个http请求,你都要进行相应的反应,编写响应的函数,例如:

def application(environ, start_response):
    method = environ['request_method']
    path = environ['path_info']
    if method=='get' and path=='/':
        return handle_home(environ, start_response)
    if method=='post' and path='/signin':
        return handle_signin(environ, start_response)

显然这会导致代码很难维护,虽然这比http接口高级不少,但还是很麻烦,因此就出现了很多的框架,flask,django等等。这使得我们编写程序很简单,我们只需要关注实际的处理函数即可,而不必自己去编写wsgi处理函数。

简单介绍一下Flask框架编写的应用。flask是基于werkzeug,所以我又看了看werkzeug的源代码。针对于wsgi函数,werkzeug进行了封装,只需要进行如下操作,例子如下:

def index():
    return response('index page')

def application(environ, start_response):
    path = environ.get('path_info') or '/'
    if path == '/':
        response = index()
    else:
        response = response('not found', status=404)
    return response(environ, start_response)

从函数中可以看到,我们不必再封装http headers,至于怎么实现的就需要看werkzeug的code了。

在flask中,werkzeug的这些都不用开发者做了,直接用如下形式:

@app.route('/)
def index():
    return 'hello,haibo'

从中可以看出,flask框架让我们完全把精力放在逻辑设计中,专注于写相应处理代码。

--------EOF---------
微信分享/微信扫码阅读