全稱為Web Server Gateway Interface
,即 Web服務(wù)器網(wǎng)關(guān)接口瓮下。是一種標(biāo)準(zhǔn)接口規(guī)范翰铡,規(guī)定了 web 服務(wù)器 和 Python web 應(yīng)用/框架 之間如何傳遞數(shù)據(jù)钝域,以便 web 應(yīng)用 可以與多種 web 服務(wù)器配合工作。
HTTP 客戶端 --- web 服務(wù)器 --- WSGI --- Flask
作用:
- 讓 web 服務(wù)器知道如何調(diào)用 web 應(yīng)用锭魔,傳遞用戶的請(qǐng)求給應(yīng)用
- 讓應(yīng)用知道用戶的請(qǐng)求內(nèi)容例证,以及如何返回消息給 web 服務(wù)器
WSGI 的兩種角色
server/gateway, 通常是 web 服務(wù)器,接受客戶的請(qǐng)求迷捧,調(diào)用 application织咧,將 application 處理的結(jié)果封裝成 HTTP 響應(yīng)返回給客戶。
application/framework, 是 Python 應(yīng)用
application 是一個(gè)需要兩個(gè)參數(shù)的可調(diào)用對(duì)象漠秋,可以是一個(gè)函數(shù)笙蒙、方法,或一個(gè)有__call__
方法的實(shí)例膛堤。
角色的實(shí)現(xiàn)
application 端 : 由 Python 框架實(shí)現(xiàn)手趣,會(huì)提供接口讓開發(fā)者能夠獲取到請(qǐng)求內(nèi)容晌该,并幫助進(jìn)行響應(yīng)返回
server 端 : 一般 web 服務(wù)器 不內(nèi)置對(duì) WSGI 的支持肥荔,需要通過擴(kuò)展來完成,比如 Apache 的 mod_wsgi 擴(kuò)展模塊朝群、Nginx 的 uWSGI燕耿。擴(kuò)展可以實(shí)現(xiàn) WSGI 的服務(wù)端、進(jìn)程管理姜胖、對(duì) application 的調(diào)用
application
在 web 框架中定義
這里舉了兩個(gè) application 對(duì)象的例子誉帅,一個(gè)是通過函數(shù)實(shí)現(xiàn),另一個(gè)通過類實(shí)現(xiàn)
HELLO_WORLD = b"Hello world!\n"
def simple_app(environ, start_response):
"""Simplest possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
start_response(status, response_headers)
return [HELLO_WORLD]
HELLO_WORLD = b"Hello world!\n"
class AppClass:
"""Produce the same output, but using a class
(Note: 'AppClass' is the "application" here, so calling it
returns an instance of 'AppClass', which is then the iterable
return value of the "application callable" as required by
the spec.
If we wanted to use *instances* of 'AppClass' as application
objects instead, we would have to implement a '__call__'
method, which would be invoked to execute the application,
and we would need to create an instance for use by the
server or gateway.
"""
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
status = '200 OK'
response_headers = [('Content-type', 'text/plain')]
self.start(status, response_headers)
yield HELLO_WORLD
server 調(diào)用 application 對(duì)象
每個(gè) web 應(yīng)用只有一個(gè)入口右莱,就是按照 WSGI 規(guī)范定義的 application蚜锨,這個(gè)可調(diào)用對(duì)象在 Python 應(yīng)用的一個(gè)文件/模塊(入口文件)中定義。
每當(dāng) web 服務(wù)器從一個(gè) HTTP 客戶端收到請(qǐng)求慢蜓,便會(huì)調(diào)用這個(gè) application亚再,將用戶請(qǐng)求傳遞給 web 應(yīng)用。
server 調(diào)用 application 時(shí)傳遞兩個(gè)參數(shù)
application
對(duì)象必須接受兩個(gè)位置參數(shù):environ
和start_response
晨抡,對(duì)參數(shù)名稱沒有強(qiáng)制要求氛悬。因此,server/gateway 在調(diào)用application
對(duì)象時(shí)耘柱,必須產(chǎn)生并傳遞兩個(gè)位置參數(shù)如捅,而不是關(guān)鍵字參數(shù)。比如调煎,這樣調(diào)用result = application(environ, start_response)
镜遣。
environ
參數(shù)是一個(gè)字典對(duì)象, 包含 CGI 規(guī)范中定義的environment
變量。這個(gè)對(duì)象必須是一個(gè) Python 內(nèi)建的字典, application 可以根據(jù)需要修改內(nèi)容士袄。字典還必須包含 WSGI 規(guī)范要求的變量, 以及 server 指定的擴(kuò)展變量烈涮、任意的操作系統(tǒng)的環(huán)境變量鱼响。
environ
中常用的成員,首先是CGI規(guī)范中要求必須包含的變量煤惩,除非值為空字符串:
- REQUEST_METHOD: HTTP 請(qǐng)求方法笛质,是個(gè)字符串,'GET'讶舰、 'POST'等
- SCRIPT_NAME: HTTP請(qǐng)求的path中的用于查找到application對(duì)象的部分鞍盗,比如Web服務(wù)器可以根據(jù)path的一部分來決定請(qǐng)求由哪個(gè)virtual host處理
- PATH_INFO: HTTP請(qǐng)求的path中剩余的部分,也就是application要處理的部分
- QUERY_STRING: HTTP請(qǐng)求中的查詢字符串跳昼,URL中?后面的內(nèi)容
- CONTENT_TYPE: HTTP headers中的content-type內(nèi)容
- CONTENT_LENGTH: HTTP headers中的content-length內(nèi)容
- SERVER_NAME 和 SERVER_PORT: 服務(wù)器名和端口般甲,這兩個(gè)值和前面的SCRIPT_NAME, PATH_INFO拼起來可以得到完整的URL路徑
- SERVER_PROTOCOL: HTTP協(xié)議版本,HTTP/1.0或者HTTP/1.1
- HTTP_: 和HTTP請(qǐng)求中的headers對(duì)應(yīng)鹅颊。
- WSGI規(guī)范中還要求environ包含下列成員:
WSGI規(guī)范中要求必須有的environ變量:
- wsgi.version:表示W(wǎng)SGI版本敷存,一個(gè)元組(1, 0),表示版本1.0
- wsgi.url_scheme:http或者h(yuǎn)ttps
- wsgi.input:一個(gè)類文件的輸入流堪伍,application可以通過這個(gè)獲取HTTP request body
- wsgi.errors:一個(gè)輸出流锚烦,當(dāng)應(yīng)用程序出錯(cuò)時(shí),可以將錯(cuò)誤信息寫入這里
- wsgi.multithread:當(dāng)application對(duì)象可能被多個(gè)線程同時(shí)調(diào)用時(shí)帝雇,這個(gè)值需要為True
- wsgi.multiprocess:當(dāng)application對(duì)象可能被多個(gè)進(jìn)程同時(shí)調(diào)用時(shí)涮俄,這個(gè)值需要為True
- wsgi.run_once:當(dāng)server期望application對(duì)象在進(jìn)程的生命周期內(nèi)只被調(diào)用一次時(shí),該值為True
start_response
是一個(gè)可調(diào)用的對(duì)象尸闸,接受兩個(gè)必須的位置參數(shù)和一個(gè)可選的參數(shù)彻亲。通常命名為status
,response_headers
和exc_info
,但不強(qiáng)制吮廉。start_response
的定義方式:start_response(status, response_headers)
苞尝。
status
參數(shù)是一個(gè)表示 HTTP 響應(yīng)狀態(tài)的字符串, 例如200 ok
。response_headers
是一個(gè)列表宦芦,由多個(gè)(header_name, header_value)
元組組成宙址,描述 HTTP 響應(yīng)頭部∽倏酰可選參數(shù)exc_info
曼氛,僅用于 server 向客戶端報(bào)錯(cuò)并在瀏覽器中顯示錯(cuò)誤。
start_response
必須返回一個(gè)可調(diào)用的write(body_data)
令野,有一個(gè)位置參數(shù): HTTP 響應(yīng)的內(nèi)容舀患。
web 服務(wù)器的例子
import os, sys
enc, esc = sys.getfilesystemencoding(), 'surrogateescape'
def unicode_to_wsgi(u):
# Convert an environment variable to a WSGI "bytes-as-unicode" string
return u.encode(enc, esc).decode('iso-8859-1')
def wsgi_to_bytes(s):
return s.encode('iso-8859-1')
def run_with_cgi(application):
environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()} # 定義 environ
environ['wsgi.input'] = sys.stdin.buffer
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
if environ.get('HTTPS', 'off') in ('on', '1'):
environ['wsgi.url_scheme'] = 'https'
else:
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = []
def write(data):
out = sys.stdout.buffer
if not headers_set:
raise AssertionError("write() before start_response()")
elif not headers_sent:
# Before the first output, send the stored headers
status, response_headers = headers_sent[:] = headers_set
out.write(wsgi_to_bytes('Status: %s\r\n' % status))
for header in response_headers:
out.write(wsgi_to_bytes('%s: %s\r\n' % header))
out.write(wsgi_to_bytes('\r\n'))
out.write(data)
out.flush()
def start_response(status, response_headers, exc_info=None): # 定義 start_response
if exc_info:
try:
if headers_sent:
# Re-raise original exception if headers sent
raise exc_info[1].with_traceback(exc_info[2])
finally:
exc_info = None # avoid dangling circular ref
elif headers_set:
raise AssertionError("Headers already set!")
headers_set[:] = [status, response_headers]
# Note: error checking on the headers should happen here,
# *after* the headers are set. That way, if an error
# occurs, start_response can only be re-called with
# exc_info set.
return write
result = application(environ, start_response) # 調(diào)用 application
try:
for data in result:
if data: # don't send headers until body appears
write(data)
if not headers_sent:
write('') # send headers now if body was empty
finally:
if hasattr(result, 'close'):
result.close()
application 返回?cái)?shù)據(jù)
被調(diào)用的 application 對(duì)象根據(jù) environ 的內(nèi)容完成業(yè)務(wù)邏輯,并返回?cái)?shù)據(jù)給 server
- 先調(diào)用
start_response()
气破,返回status
和response_headers
給 server 作為 HTTP 響應(yīng)頭部聊浅。這同時(shí)也是一個(gè)信號(hào),告訴 server,要開始返回 HTTP 的 body 了 - 然后低匙,通過 return 返回一個(gè)可迭代對(duì)象作為 HTTP 響應(yīng)內(nèi)容旷痕,如果響應(yīng)為空,可以返回
None
這樣 server 可以按照規(guī)定的 HTTP 報(bào)文格式順序顽冶,先發(fā)送 HTTP 響應(yīng)頭部欺抗,然后再發(fā)送 HTTP 響應(yīng)的內(nèi)容。
WSGI 中間件
需要注意的是强重,有些應(yīng)用可以同時(shí)扮演 WSGI 的兩種角色/具有對(duì)應(yīng)的功能绞呈,比如中間件(middleware)。這是運(yùn)行在 server 與 application 之間的應(yīng)用间景。
對(duì)于 server 佃声,中間件是 application,而對(duì)于 application倘要,中間件是 server圾亏。
可以生成environ
, 定義start_response
, 調(diào)用application
對(duì)象。也可以執(zhí)行業(yè)務(wù)邏輯封拧,調(diào)用start_response
志鹃,并通過return
返回結(jié)果。server 獲取結(jié)果哮缺,發(fā)送給客戶弄跌。
中間件可以有多層甲喝,能夠處理所有經(jīng)過的request
和response
尝苇,比如檢查、修改埠胖。
中間件的工作過程:
上圖中最上面的三個(gè)彩色框表示角色糠溜,中間的白色框表示操作,操作的發(fā)生順序按照1 ~ 5進(jìn)行了排序直撤,我們直接對(duì)著上圖來說明middleware是如何工作的:
- Server 收到客戶端的 HTTP 請(qǐng)求后非竿,生成了
environ_s
,并且已經(jīng)定義了start_response_s
谋竖。 - Server 調(diào)用
Middleware
的application
對(duì)象红柱,傳遞的參數(shù)是environ_s
和start_response_s
。 - Middleware 會(huì)根據(jù)
environ
執(zhí)行業(yè)務(wù)邏輯蓖乘,生成environ_m
锤悄,并且已經(jīng)定義了start_response_m
。 - Middleware 決定調(diào)用 Application 的 application 對(duì)象嘉抒,傳遞參數(shù)是
environ_m
和start_response_m
零聚。Application 的 application 對(duì)象處理完成后,會(huì)調(diào)用start_response_m
并且返回結(jié)果給Middleware ,存放在result_m
中隶症。 - Middleware 處理
result_m
政模,然后生成result_s
,接著調(diào)用start_response_s
蚂会,并返回結(jié)果result_s
給 Server 端淋样。Server 端獲取到result_s
后就可以發(fā)送結(jié)果給客戶端了。
web 框架 WSGI application 端代碼
Pyramid
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
return Response(
'Hello world from Pyramid!n',
content_type='text/plain',
)
config = Configurator()
config.add_route('hello', '/hello')
config.add_view(hello_world, route_name='hello')
app = config.make_wsgi_app()
pyramid.config.__init__.py
from pyramid.router import Router
class Configurator(
TestingConfiguratorMixin,
TweensConfiguratorMixin,
SecurityConfiguratorMixin,
ViewsConfiguratorMixin,
RoutesConfiguratorMixin,
ZCAConfiguratorMixin,
I18NConfiguratorMixin,
RenderingConfiguratorMixin,
AssetsConfiguratorMixin,
SettingsConfiguratorMixin,
FactoriesConfiguratorMixin,
AdaptersConfiguratorMixin,
):
"""
A Configurator is used to configure a :app:`Pyramid`
:term:`application registry`.
"""
def make_wsgi_app(self):
self.commit()
app = Router(self.registry)
global_registries.add(self.registry)
self.manager.push({'registry':self.registry, 'request':None})
try:
self.registry.notify(ApplicationCreated(app))
finally:
self.manager.pop()
return app
pyramid.Router.py
@implementer(IRouter)
class Router(object):
debug_notfound = False
debug_routematch = False
threadlocal_manager = manager
def __init__(self, registry):
q = registry.queryUtility
self.logger = q(IDebugLogger)
self.root_factory = q(IRootFactory, default=DefaultRootFactory)
self.routes_mapper = q(IRoutesMapper)
self.request_factory = q(IRequestFactory, default=Request)
self.request_extensions = q(IRequestExtensions)
tweens = q(ITweens)
if tweens is None:
tweens = excview_tween_factory
self.orig_handle_request = self.handle_request
self.handle_request = tweens(self.handle_request, registry)
self.root_policy = self.root_factory # b/w compat
self.registry = registry
settings = registry.settings
if settings is not None:
self.debug_notfound = settings['debug_notfound']
self.debug_routematch = settings['debug_routematch']
def handle_request(self, request):
attrs = request.__dict__
registry = attrs['registry']
request.request_iface = IRequest
context = None
routes_mapper = self.routes_mapper
debug_routematch = self.debug_routematch
adapters = registry.adapters
has_listeners = registry.has_listeners
notify = registry.notify
logger = self.logger
has_listeners and notify(NewRequest(request))
# find the root object
root_factory = self.root_factory
if routes_mapper is not None:
info = routes_mapper(request)
match, route = info['match'], info['route']
if route is None:
if debug_routematch:
msg = ('no route matched for url %s' %
request.url)
logger and logger.debug(msg)
else:
attrs['matchdict'] = match
attrs['matched_route'] = route
if debug_routematch:
msg = (
'route matched for url %s; '
'route_name: %r, '
'path_info: %r, '
'pattern: %r, '
'matchdict: %r, '
'predicates: %r' % (
request.url,
route.name,
request.path_info,
route.pattern,
match,
', '.join([p.text() for p in route.predicates]))
)
logger and logger.debug(msg)
request.request_iface = registry.queryUtility(
IRouteRequest,
name=route.name,
default=IRequest)
root_factory = route.factory or self.root_factory
root = root_factory(request)
attrs['root'] = root
# find a context
traverser = adapters.queryAdapter(root, ITraverser)
if traverser is None:
traverser = ResourceTreeTraverser(root)
tdict = traverser(request)
context, view_name, subpath, traversed, vroot, vroot_path = (
tdict['context'],
tdict['view_name'],
tdict['subpath'],
tdict['traversed'],
tdict['virtual_root'],
tdict['virtual_root_path']
)
attrs.update(tdict)
has_listeners and notify(ContextFound(request))
# find a view callable
context_iface = providedBy(context)
response = _call_view(
registry,
request,
context,
context_iface,
view_name
)
if response is None:
if self.debug_notfound:
msg = (
'debug_notfound of url %s; path_info: %r, '
'context: %r, view_name: %r, subpath: %r, '
'traversed: %r, root: %r, vroot: %r, '
'vroot_path: %r' % (
request.url, request.path_info, context,
view_name, subpath, traversed, root, vroot,
vroot_path)
)
logger and logger.debug(msg)
else:
msg = request.path_info
raise HTTPNotFound(msg)
return response
def invoke_subrequest(self, request, use_tweens=False):
registry = self.registry
has_listeners = self.registry.has_listeners
notify = self.registry.notify
threadlocals = {'registry':registry, 'request':request}
manager = self.threadlocal_manager
manager.push(threadlocals)
request.registry = registry
request.invoke_subrequest = self.invoke_subrequest
if use_tweens:
handle_request = self.handle_request
else:
handle_request = self.orig_handle_request
try:
try:
extensions = self.request_extensions
if extensions is not None:
apply_request_extensions(request, extensions=extensions)
response = handle_request(request)
if request.response_callbacks:
request._process_response_callbacks(response)
has_listeners and notify(NewResponse(request, response))
return response
finally:
if request.finished_callbacks:
request._process_finished_callbacks()
finally:
manager.pop()
def __call__(self, environ, start_response): # 按照 WSGI 規(guī)范定義的 application
"""
Accept ``environ`` and ``start_response``; create a
:term:`request` and route the request to a :app:`Pyramid`
view based on introspection of :term:`view configuration`
within the application registry; call ``start_response`` and
return an iterable.
"""
request = self.request_factory(environ)
response = self.invoke_subrequest(request, use_tweens=True)
return response(request.environ, start_response)
flask
from flask import Flask
from flask import Response
flask_app = Flask('flaskapp')
@flask_app.route('/hello')
def hello_world():
return Response(
'Hello world from Flask!n',
mimetype='text/plain'
)
app = flask_app.wsgi_app
flask.app.py
class Flask(_PackageBoundObject):
"""The flask object implements a WSGI application and acts as the central
object. It is passed the name of the module or package of the
application. Once it is created it will act as a central registry for
the view functions, the URL rules, template configuration and much more.
The name of the package is used to resolve resources from inside the
package or the folder the module is contained in depending on if the
package parameter resolves to an actual python package (a folder with
an `__init__.py` file inside) or a standard module (just a `.py` file).
For more information about resource loading, see :func:`open_resource`.
Usually you create a :class:`Flask` instance in your main module or
in the `__init__.py` file of your package like this::
from flask import Flask
app = Flask(__name__)
.. admonition:: About the First Parameter
The idea of the first parameter is to give Flask an idea what
belongs to your application. This name is used to find resources
on the file system, can be used by extensions to improve debugging
information and a lot more.
So it's important what you provide there. If you are using a single
module, `__name__` is always the correct value. If you however are
using a package, it's usually recommended to hardcode the name of
your package there.
For example if your application is defined in `yourapplication/app.py`
you should create it with one of the two versions below::
app = Flask('yourapplication')
app = Flask(__name__.split('.')[0])
Why is that? The application will work even with `__name__`, thanks
to how resources are looked up. However it will make debugging more
painful. Certain extensions can make assumptions based on the
import name of your application. For example the Flask-SQLAlchemy
extension will look for the code in your application that triggered
an SQL query in debug mode. If the import name is not properly set
up, that debugging information is lost. (For example it would only
pick up SQL queries in `yourapplication.app` and not
`yourapplication.views.frontend`)
"""
#: Default configuration parameters.
default_config = ImmutableDict({
'DEBUG': False,
'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'PRESERVE_CONTEXT_ON_EXCEPTION': None,
'SECRET_KEY': None,
'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
'USE_X_SENDFILE': False,
'LOGGER_NAME': None,
'SERVER_NAME': None,
'APPLICATION_ROOT': None,
'SESSION_COOKIE_NAME': 'session',
'SESSION_COOKIE_DOMAIN': None,
'SESSION_COOKIE_PATH': None,
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'MAX_CONTENT_LENGTH': None,
'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False,
'PREFERRED_URL_SCHEME': 'http',
'JSON_AS_ASCII': True,
'JSON_SORT_KEYS': True,
'JSONIFY_PRETTYPRINT_REGULAR': True,
})
def __init__(self, import_name, static_path=None, static_url_path=None,
static_folder='static', template_folder='templates',
instance_path=None, instance_relative_config=False):
_PackageBoundObject.__init__(self, import_name,
template_folder=template_folder)
if static_path is not None:
from warnings import warn
warn(DeprecationWarning('static_path is now called '
'static_url_path'), stacklevel=2)
static_url_path = static_path
if static_url_path is not None:
self.static_url_path = static_url_path
if static_folder is not None:
self.static_folder = static_folder
if instance_path is None:
instance_path = self.auto_find_instance_path()
elif not os.path.isabs(instance_path):
raise ValueError('If an instance path is provided it must be '
'absolute. A relative path was given instead.')
self.instance_path = instance_path
self.config = self.make_config(instance_relative_config)
# Prepare the deferred setup of the logger.
self._logger = None
self.logger_name = self.import_name
self.view_functions = {}
self._error_handlers = {}
self.error_handler_spec = {None: self._error_handlers}
self.url_build_error_handlers = []
self.before_request_funcs = {}
self.before_first_request_funcs = []
self.after_request_funcs = {}
self.teardown_request_funcs = {}
self.teardown_appcontext_funcs = []
self.url_value_preprocessors = {}
self.url_default_functions = {}
self.template_context_processors = {
None: [_default_template_ctx_processor]
}
self.blueprints = {}
self.extensions = {}
self.url_map = Map()
self._got_first_request = False
self._before_request_lock = Lock()
if self.has_static_folder:
self.add_url_rule(self.static_url_path + '/<path:filename>',
endpoint='static',
view_func=self.send_static_file)
def make_response(self, rv):
"""Converts the return value from a view function to a real
response object that is an instance of :attr:`response_class`.
The following types are allowed for `rv`:
.. tabularcolumns:: |p{3.5cm}|p{9.5cm}|
======================= ===========================================
:attr:`response_class` the object is returned unchanged
:class:`str` a response object is created with the
string as body
:class:`unicode` a response object is created with the
string encoded to utf-8 as body
a WSGI function the function is called as WSGI application
and buffered as response object
:class:`tuple` A tuple in the form ``(response, status,
headers)`` where `response` is any of the
types defined here, `status` is a string
or an integer and `headers` is a list of
a dictionary with header values.
======================= ===========================================
:param rv: the return value from the view function
.. versionchanged:: 0.9
Previously a tuple was interpreted as the arguments for the
response object.
"""
status = headers = None
if isinstance(rv, tuple):
rv, status, headers = rv + (None,) * (3 - len(rv))
if rv is None:
raise ValueError('View function did not return a response')
if not isinstance(rv, self.response_class):
# When we create a response object directly, we let the constructor
# set the headers and status. We do this because there can be
# some extra logic involved when creating these objects with
# specific values (like default content type selection).
if isinstance(rv, (text_type, bytes, bytearray)):
rv = self.response_class(rv, headers=headers, status=status)
headers = status = None
else:
rv = self.response_class.force_type(rv, request.environ)
if status is not None:
if isinstance(status, string_types):
rv.status = status
else:
rv.status_code = status
if headers:
rv.headers.extend(headers)
return rv
def wsgi_app(self, environ, start_response): # 按照 WSGI 規(guī)范定義的 application
"""The actual WSGI application. This is not implemented in
`__call__` so that middlewares can be applied without losing a
reference to the class. So instead of doing this::
app = MyMiddleware(app)
It's a better idea to do this instead::
app.wsgi_app = MyMiddleware(app.wsgi_app)
Then you still have the original application object around and
can continue to call methods on it.
.. versionchanged:: 0.7
The behavior of the before and after request callbacks was changed
under error conditions and a new callback was added that will
always execute at the end of the request, independent on if an
error occurred or not. See :ref:`callbacks-and-errors`.
:param environ: a WSGI environment
:param start_response: a callable accepting a status code,
a list of headers and an optional
exception context to start the response
"""
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.make_response(self.handle_exception(e))
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
def __call__(self, environ, start_response):
"""Shortcut for :attr:`wsgi_app`."""
return self.wsgi_app(environ, start_response)
django
# -*- coding:utf-8 -*-
"""
WSGI config for helloworld project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""
# 配置 settings 模塊
import os
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "helloworld.settings")
os.environ["DJANGO_SETTINGS_MODULE"]="helloworld.settings"
'''
如果 "DJANGO_SETTINGS_MODULE"這個(gè)變量沒有設(shè)置胁住,默認(rèn) wsgi.py 設(shè)置為 mysite.settings, mysite 是你的項(xiàng)目的名稱习蓬。這是默認(rèn)情況下, runserver 發(fā)現(xiàn)配置的地方措嵌。
'''
# 應(yīng)用 WSGI middleware
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
django.core.wsgi
import django
from django.core.handlers.wsgi import WSGIHandler
def get_wsgi_application():
"""
The public interface to Django's WSGI support. Should return a WSGI
callable.
Allows us to avoid making django.core.handlers.WSGIHandler public API, in
case the internal WSGI implementation changes or moves in the future.
"""
django.setup()
return WSGIHandler()
django.core.handlers.wsgi
class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest
def __call__(self, environ, start_response): # 按照 WSGI 規(guī)范定義的 application
# Set up middleware if needed. We couldn't do this earlier, because
# settings weren't available.
if self._request_middleware is None:
with self.initLock:
try:
# Check that middleware is still uninitialized.
if self._request_middleware is None:
self.load_middleware()
except:
# Unload whatever middleware we got
self._request_middleware = None
raise
set_script_prefix(get_script_name(environ))
signals.request_started.send(sender=self.__class__, environ=environ)
try:
request = self.request_class(environ)
except UnicodeDecodeError:
logger.warning('Bad Request (UnicodeDecodeError)',
exc_info=sys.exc_info(),
extra={
'status_code': 400,
}
)
response = http.HttpResponseBadRequest()
else:
response = self.get_response(request)
response._handler_class = self.__class__
status = '%s %s' % (response.status_code, response.reason_phrase)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
start_response(force_str(status), response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response