栏目分类:
子分类:
返回
文库吧用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
文库吧 > IT > 软件开发 > 后端开发 > 架构设计

python系统架构

架构设计 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

python系统架构

在Python Web开发的世界里框架横行,但是不论是Django、Webpy、Bottlepy还是Tornado他们在处理Web请求时都有着相同的处理机制,即都要遵循WSGI协议的标准。本文首先分析Python Web开发中常用系统架构,然后对WSGI协议内容进行了简单结算,接着基于WSGI协议编写了一个简单的Python Web框架,最后以Django为例分析处理Web请求的基本流程。

Python Web开发系统架构

Python Web 开发中,服务端程序可以分为两个部分,一是服务器程序,二是应用程序。前者负责把客户端请求接收,整理,后者负责具体的逻辑处理。为了方便应用程序的开发,我们把常用的功能封装起来,成为各种Web开发框架,例如 Django, Flask, Tornado。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

Python Web开发中,这个标准就是 Python Web Server Gateway Interface, 即 WSGI。WSGI是作为Web服务器与Web应用程序或应用框架之间的一种低级别的接口,它是基于现存的CGI标准而设计的。WSGI将 Web 组件分为三类: Web服务器,Web中间件,Web应用程序, WSGI基本处理模式为 :

WSGI Server -> (WSGI Middleware)* -> WSGI Application

WSGI概述

WSGI Application

WSGI规定:

1. 应用程序需要是一个可调用的对象

在Python中:

可以是函数

可以是一个实例,它的类实现了__call__方法

可以是一个类,这时候,用这个类生成实例的过程就相当于调用这个类

同时,WSGI规定:

2. 可调用对象接收两个参数

这样,如果这个对象是函数的话,它看起来要是这个样子:

1

2

3# callable function

defapplication(environ,start_response):

pass

如果这个对象是一个类的话,它看起来是这个样子:

1

2

3

4

# callable class

classApplication:

def__init__(self,environ,start_response):

pass

如果这个对象是一个类的实例,那么,这个类看起来是这个样子:

1

2

3

4# callable object

classApplicationObj:

def__call__(self,environ,start_response):

pass

最后,WSGI还规定:3.可调用对象要返回一个值,这个值是可迭代的。

这样的话,前面的三个例子就变成:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

HELLO_WORLD=b"Hello

world!n"

# callable function

defapplication(environ,start_response):

return[HELLO_WORLD]

# callable class

classApplication:

def__init__(self,environ,start_response):

pass

def__iter__(self):

yieldHELLO_WORLD

# callable object

classApplicationObj:

def__call__(self,environ,start_response):

return[HELLO_WORLD]

你可能会说,不是啊,我们平时写的web程序不是这样啊。 比如如果使用web.py框架的话,一个典型的应用可能是这样的:

1

2

3classhello:

defGET(self):

return'Hello, world!'

这是由于框架已经把WSGI中规定的一些东西封装起来了,我们平时用框架时,看不到这些东西,只需要直接实现我们的逻辑,再返回一个值就好了。其它的东西框架帮我们做好了。这也是框架的价值所在,把常用的东西封装起来,让使用者只需要关注最重要的东西。

当然,WSGI关于应用程序的规定不只这些,但是现在,我们只需要知道这些就足够了。

WSGI Server/Gateway

服务器程序会在每次客户端的请求传来时,调用我们写好的应用程序,并将处理好的结果返回给客户端。4.服务器程序需要调用应用程序

服务器程序看起来大概是这个样子的:

1

2

3

4

5

6

7

8

9

10

11

12

13

defrun(application):

environ={}

defstart_response(status,response_headers,exc_info=None):

pass

result=application(environ,start_response)

defwrite(data):

pass

fordatainresult:

write(data)

这里可以看出服务器程序是如何与应用程序配合完成用户请求的。

WSGI规定了应用程序需要一个可调用对象,有两个参数,返回一个可迭代对象。在服务器 程序中,针对这几个规定,做了以下几件事:

把应用程序需要的两个参数设置好

调用应用程序

迭代访问应用程序的返回结果,并将其传回客户端

你可以从中发现,应用程序需要的两个参数,一个是一个dict对象,一个是函数。

middleware

另外,有些功能可能介于服务器程序和应用程序之间,例如,服务器拿到了客户端请求的URL, 不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing,这个功能就可以放在二者中间实现,这个中间层就是 middleware。

middleware对服务器程序和应用是透明的,也就是说,服务器程序以为它就是应用程序,而应用程序以为它就是服务器。这就告诉我们,middleware需要把自己伪装成一个服务器,接受应用程序,调用它,同时middleware还需要把自己伪装成一个应用程序,传给服务器程序。

其实无论是服务器程序,middleware 还是应用程序,都在服务端,为客户端提供服务,之所以把他们抽象成不同层,就是为了控制复杂度,使得每一次都不太复杂,各司其职。

下面,我们看看middleware大概是什么样子的。

1

2

3

4

5

6

7

8

9

10

11# URL Routing middleware

defurlrouting(url_app_mapping):

defmidware_app(environ,start_response):

url=environ['PATH_INFO']

app=url_app_mapping[url]

result=app(environ,start_response)

returnresult

returnmidware_app

函数 midware_app就是一个简单的middleware:对服务器而言,它是一个应用程序,是一个可调用对象, 有两个参数,返回一个可调用对象。对应用程序而言,它是一个服务器,为应用程序提供了参数,并且调用了应用程序。

另外,这里的urlrouting函数,相当于一个函数生成器,你给它不同的 url-app 映射关系,它会生成相应的具有 url routing功能的 middleware。

编写符合WSGI协议的Python框架

WSGI Application就是一个普通的Callable对象,当有请求到来时,WSGI Server会调用这个WSGI Application。这个对象接收两个参数,通常为environ,start_response。environ可以理解为环境变量,跟一次请求相关的所有信息都保存在了这个环境变量中,包括服务器信息,客户端信息,请求信息。start_response是一个callback函数,WSGI Application通过调用start_response,将Response Headers/Status 返回给WSGI Server。此外这个WSGI Application会返回一个iterator对象 ,这个iterator就是Response Body。

1

2

3

4

5

defsimple_app(environ,start_response):

status='200 OK'

response_headers=[('Content-type','text/plain')]

start_response(status,response_headers)

return[u"This

is hello wsgi app".encode('utf8')]

WSGI Server可以理解为一个符合WSGI规范的Webserver,接收Request请求,封装一系列环境变量,按照WSGI规范调用注册的WSGI Application,最后将Response返回给客户端。以python自带的wsgiref为例,wsgiref是按照WSGI规范实现的一个简单WSGI Server。

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信息塞回给客户端。

结合服务器和应用程序可以得到一个实例

1

2

3

4

5

6

7

8

9

10

11fromwsgiref.simple_serverimportmake_server

defsimple_app(environ,start_response):

status='200 OK'

response_headers=[('Content-type','text/plain')]

start_response(status,response_headers)

return[u"This is hello wsgi app".encode('utf8')]

httpd=make_server('',8000,simple_app)

print"Serving on port 8000..."

httpd.serve_forever()

上面的application看起来没什么意思,感觉没有太大用,但加上一层层的middleware包装之后就不一样了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

fromwsgiref.simple_serverimportmake_server

URL_PATTERNS=(

('hi/','say_hi'),

('hello/','say_hello'),

)

classDispatcher(object):

def_match(self,path):

path=path.split('/')[1]

forurl,appinURL_PATTERNS:

ifpathinurl:

returnapp

def__call__(self,environ,start_response):

path=environ.get('PATH_INFO','/')

app=self._match(path)

ifapp:

app=globals()[app]

returnapp(environ,start_response)

else:

start_response("404

NOT FOUND",[('Content-type','text/plain')])

return["Page

dose not exists!"]

defsay_hi(environ,start_response):

start_response("200

OK",[('Content-type','text/html')])

return["Eureka

say hi to you!"]

defsay_hello(environ,start_response):

start_response("200

OK",[('Content-type','text/html')])

return["Eureka

say hello to you!"]

app=Dispatcher()

httpd=make_server('',8000,app)

print"Serving on port 8000..."

httpd.serve_forever()

上面的例子可以看出来,middleware 包装之后,一个简单wsgi app就有了URL dispatch功能。然后我还可以在这个app外面再加上其它的middleware来包装它,例如加一个权限认证的middleware:

1

2

3

4

5

6

7

8

9

10

11

12

13

14classAuth(object):

def__init__(self,app):

self.app=app

def__call__(self,environ,start_response):

#TODO

returnself.app(environ,start_response)

app=Dispatcher()

auth_app=Auth(app)

httpd=make_server('',8000,auth_app)

print"Serving on port 8000..."

httpd.serve_forever()

经过这些middleware的包装,已经有点框架的感觉了。其实基于wsgi的框架,例如paste,pylons就是这样通过一层层middleware组合起来的。只是一个成熟的框架,这样的middleware会有很多,例如:

1

2

3

4

5

6

defconfigure(app):

returnErrorHandlerMiddleware(

SessionMiddleware(

IdentificationMiddleware(

AuthenticationMiddleware(

UrlParserMiddleware(app))))))

同时我们对Application进行改造,抽象出一个简单的框架:

剥离出其中的具体处理细节:urls配置 和 web处理逻辑(改成在多个类中实现相应的GET方法)

把方法header实现为类方法(classmethod),以方便外部作为功能函数调用

改用 具有 __call__ 方法的 实例 来实现application

形成框架文件framework.py

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""framework.py"""

importre

classmy_app:

"""my simple web framework"""

headers=[]

def__init__(self,urls=(),fvars={}):

self._urls=urls

self._fvars=fvars

self._status='200 OK'

def__call__(self,environ,start_response):

delself.headers[:]# 在每次作出响应前,清空上一次的headers

result=self._delegate(environ)

start_response(self._status,self.headers)

# 将返回值result(字符串 或者 字符串列表)转换为迭代对象

ifisinstance(result,basestring):

returniter([result])

else:

returniter(result)

def_delegate(self,environ):

path=environ['PATH_INFO']

method=environ['REQUEST_METHOD']

forpattern,nameinself._urls:

m=re.match('^'+pattern+'$',path)

ifm:

# pass the matched groups as arguments to the function

args=m.groups()

funcname=method.upper()# 方法名大写(如GET、POST)

klass=self._fvars.get(name)# 根据字符串名称查找类对象

ifhasattr(klass,funcname):

func=getattr(klass,funcname)

returnfunc(klass(),*args)

returnself._notfound()

def_notfound(self):

self.status='404 Not Found'

self.header('Content-type','text/plain')

return"Not Foundn"

@classmethod

defheader(clazz,name,value):

clazz.headers.append((name,value))

相应的应用程序文件code.py:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

#!/usr/bin/env python

# -*- coding: utf-8 -*-

"""code.py"""

fromwsgiref.simple_serverimportmake_server

fromframeworkimportmy_app

urls=(

("/","index"),

("/hello/(.*)","hello"),

)

classindex:

defGET(self):

my_app.header('Content-type','text/plain')

return"Welcome!n"

classhello:

defGET(self,name):

my_app.header('Content-type','text/plain')

return"Hello %s!n"%name

wsgiapp=my_app(urls,globals())

if__name__=='__main__':

httpd=make_server('',8086,wsgiapp)

sa=httpd.socket.getsockname()

print'http://{0}:{1}/'.format(*sa)

#

Respond to requests until process is killed

httpd.serve_forever()

Django处理Web请求基本流程

Django和其他Web框架一样,HTTP的处理流程基本类似:接受request,返回response内容。Django的具体处理流程大致如下:

加载project settings

在通过django-admin.py创建project的时候,Django会自动生成默认的settings文件和manager.py等文件,在创建WSGIServer之前会执行下面的引用:

1fromdjango.confimportsettings

上面引用在执行时,会读取os.environ中的DJANGO_SETTINGS_MODULE配置,加载项目配置文件,生成settings对象。所以,在manager.py文件中你可以看到,在获取WSGIServer之前,会先将project的settings路径加到os路径中。

创建WSGIServer

不管是使用runserver还是uWSGI运行Django项目,在启动时都会调用django.core.servers.basehttp中的run()方法,创建一个django.core.servers.basehttp.WSGIServer类的实例,之后调用其serve_forever()方法启动HTTP服务。run方法的源码如下:

1

2

3

4

5

6

7

8

9

10

defrun(addr,port,wsgi_handler,ipv6=False,threading=False):

server_address=(addr,port)

ifthreading:

httpd_cls=type(str('WSGIServer'),(socketserver.ThreadingMixIn,WSGIServer),{})

else:

httpd_cls=WSGIServer

httpd=httpd_cls(server_address,WSGIRequestHandler,ipv6=ipv6)

#

Sets the callable application as the WSGI application that will receive requests

httpd.set_app(wsgi_handler)

httpd.serve_forever()

如上,我们可以看到:在创建WSGIServer实例的时候会指定HTTP请求的Handler,上述代码使用WSGIRequestHandler。当用户的HTTP请求到达服务器时,WSGIServer会创建WSGIRequestHandler实例,使用其handler方法来处理HTTP请求(其实最终是调用wsgiref.handlers.BaseHandler中的run方法处理)。WSGIServer通过set_app方法设置一个可调用(callable)的对象作为application,上面提到的handler方法最终会调用设置的application处理request,并返回response。

其中,WSGIServer继承自wsgiref.simple_server.WSGIServer,而WSGIRequestHandler继承自wsgiref.simple_server.WSGIRequestHandler,wsgiref是Python标准库给出的WSGI的参考实现。其源码可自行到wsgiref参看,这里不再细说。

处理Request

第二步中说到的application,在Django中一般是django.core.handlers.wsgi.WSGIHandler对象,WSGIHandler继承自django.core.handlers.base.BaseHandler,这个是Django处理request的核心逻辑,它会创建一个WSGIRequest实例,而WSGIRequest是从http.HttpRequest继承而来

返回Response

上面提到的BaseHandler中有个get_response方法,该方法会先加载Django项目的ROOT_URLCONF,然后根据url规则找到对应的view方法(类),view逻辑会根据request实例生成并返回具体的response。

在Django返回结果之后,第二步中提到wsgiref.handlers.BaseHandler.run方法会调用finish_response结束请求,并将内容返回给用户。

Django在处理request的时候其实做了很多事情,下面两张Django流程图能基本说明问题。

  

1. 用户通过浏览器请求一个页面

2. 请求到达Request Middlewares,中间件对request做一些预处理或者直接response请求

3. URLConf通过urls.py文件和请求的URL找到相应的View

4. View Middlewares被访问,它同样可以对request做一些处理或者直接返回response

5. 调用View中的函数

6. View中的方法可以选择性的通过Models访问底层的数据

7. 所有的Model-to-DB的交互都是通过manager完成的

8. 如果需要,Views可以使用一个特殊的Context

9. Context被传给Template用来生成页面

a. Template使用Filters和Tags去渲染输出

b. 输出被返回到View

c. HTTPResponse被发送到Response Middlewares

d. 任何Response Middlewares都可以丰富response或者返回一个完全不同的response

e. Response返回到浏览器,呈现给用户

上述流程中最主要的几个部分分别是:Middleware(中间件,包括request, view, exception, response),URLConf(url映射关系),Template(模板系统),下面一一介绍一下。

Middleware(中间件)

Middleware并不是Django所独有的东西,在其他的Web框架中也有这种概念。在Django中,Middleware可以渗入处理流程的四个阶段:request,view,response和exception,相应的,在每个Middleware类中都有process_request,process_view, process_response 和 process_exception这四个方法。你可以定义其中任意一个活多个方法,这取决于你希望该Middleware作用于哪个处理阶段。每个方法都可以直接返回response对象。

Middleware是在Django BaseHandler的load_middleware方法执行时加载的,加载之后会建立四个列表作为处理器的实例变量:

_request_middleware:process_request方法的列表

_view_middleware:process_view方法的列表

_response_middleware:process_response方法的列表

_exception_middleware:process_exception方法的列表

Django的中间件是在其配置文件(settings.py)的MIDDLEWARE_CLASSES元组中定义的。在MIDDLEWARE_CLASSES中,中间件组件用字符串表示:指向中间件类名的完整Python路径。

URLConf(URL映射)

如果处理request的中间件都没有直接返回response,那么Django会去解析用户请求的URL。URLconf就是Django所支撑网站的目录。它的本质是URL模式以及要为该URL模式调用的视图函数之间的映射表。通过这种方式可以告诉Django,对于这个URL调用这段代码,对于那个URL调用那段代码。具体的,在Django项目的配置文件中有ROOT_URLCONF常量,这个常量加上根目录”/”,作为参数来创建django.core.urlresolvers.RegexURLResolver的实例,然后通过它的resolve方法解析用户请求的URL,找到第一个匹配的view。

其他有关URLConf的内容,这里不再具体介绍,大家可以看DjangoBook了解。

Template(模板)

大部分web框架都有自己的Template(模板)系统,Django也是。但是,Django模板不同于Mako模板和jinja2模板,在Django模板不能直接写Python代码,只能通过额外的定义filter和template tag实现。由于本文主要介绍Django流程,模板内容就不过多介绍。

参考文献

转载请注明:文章转载自 www.wk8.com.cn
本文地址:https://www.wk8.com.cn/it/912930.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 wk8.com.cn

ICP备案号:晋ICP备2021003244-6号