全称为Web Server Gateway Interface,即 Web服务器网关接口。是一种标准接口规范,规定了 web 服务器 和 Python web 应用/框架 之间如何传递数据,以便 web 应用 可以与多种 web 服务器配合工作。

HTTP 客户端 --- web 服务器 --- WSGI --- Flask

作用:

  • 让 web 服务器知道如何调用 web 应用,传递用户的请求给应用
  • 让应用知道用户的请求内容,以及如何返回消息给 web 服务器

WSGI 的两种角色

server/gateway, 通常是 web 服务器,接受客户的请求,调用 application,将 application 处理的结果封装成 HTTP 响应返回给客户。

application/framework, 是 Python 应用

application 是一个需要两个参数的可调用对象,可以是一个函数、方法,或一个有__call__方法的实例。

角色的实现

application 端 : 由 Python 框架实现,会提供接口让开发者能够获取到请求内容,并帮助进行响应返回

server 端 : 一般 web 服务器 不内置对 WSGI 的支持,需要通过扩展来完成,比如 Apache 的 mod_wsgi 扩展模块、Nginx 的 uWSGI。扩展可以实现 WSGI 的服务端、进程管理、对 application 的调用

application

在 web 框架中定义

这里举了两个 application 对象的例子,一个是通过函数实现,另一个通过类实现

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 itreturns an instance of 'AppClass', which is then the iterablereturn value of the "application callable" as required bythe spec.If we wanted to use *instances* of 'AppClass' as applicationobjects 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 theserver or gateway."""def __init__(self, environ, start_response):self.environ = environself.start = start_responsedef __iter__(self):status = '200 OK'response_headers = [('Content-type', 'text/plain')]self.start(status, response_headers)yield HELLO_WORLD

server 调用 application 对象

每个 web 应用只有一个入口,就是按照 WSGI 规范定义的 application,这个可调用对象在 Python 应用的一个文件/模块(入口文件)中定义。

每当 web 服务器从一个 HTTP 客户端收到请求,便会调用这个 application,将用户请求传递给 web 应用。

server 调用 application 时传递两个参数

application对象必须接受两个位置参数:environstart_response,对参数名称没有强制要求。因此,server/gateway 在调用application对象时,必须产生并传递两个位置参数,而不是关键字参数。比如,这样调用result = application(environ, start_response)

environ参数是一个字典对象, 包含 CGI 规范中定义的environment变量。这个对象必须是一个 Python 内建的字典, application 可以根据需要修改内容。字典还必须包含 WSGI 规范要求的变量, 以及 server 指定的扩展变量、任意的操作系统的环境变量。

environ中常用的成员,首先是CGI规范中要求必须包含的变量,除非值为空字符串:

  • REQUEST_METHOD: HTTP 请求方法,是个字符串,'GET'、 'POST'等
  • SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据path的一部分来决定请求由哪个virtual host处理
  • PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分
  • QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容
  • CONTENT_TYPE: HTTP headers中的content-type内容
  • CONTENT_LENGTH: HTTP headers中的content-length内容
  • SERVER_NAME 和 SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径
  • SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
  • HTTP_: 和HTTP请求中的headers对应。
  • WSGI规范中还要求environ包含下列成员:

WSGI规范中要求必须有的environ变量:

  • wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0
  • wsgi.url_scheme:http或者https
  • wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body
  • wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里
  • wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True
  • wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True
  • wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True

start_response是一个可调用的对象,接受两个必须的位置参数和一个可选的参数。通常命名为status,response_headersexc_info,但不强制。start_response的定义方式:start_response(status, response_headers)

status参数是一个表示 HTTP 响应状态的字符串, 例如200 okresponse_headers是一个列表,由多个(header_name, header_value)元组组成,描述 HTTP 响应头部。可选参数exc_info,仅用于 server 向客户端报错并在浏览器中显示错误。

start_response必须返回一个可调用的write(body_data),有一个位置参数: HTTP 响应的内容。

web 服务器的例子

import os, sysenc, esc = sys.getfilesystemencoding(), 'surrogateescape'def unicode_to_wsgi(u):# Convert an environment variable to a WSGI "bytes-as-unicode" stringreturn 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()}   # 定义 environenviron['wsgi.input']        = sys.stdin.bufferenviron['wsgi.errors']       = sys.stderrenviron['wsgi.version']      = (1, 0)environ['wsgi.multithread']  = Falseenviron['wsgi.multiprocess'] = Trueenviron['wsgi.run_once']     = Trueif 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.bufferif not headers_set:raise AssertionError("write() before start_response()")elif not headers_sent:# Before the first output, send the stored headersstatus, response_headers = headers_sent[:] = headers_setout.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_responseif exc_info:try:if headers_sent:# Re-raise original exception if headers sentraise exc_info[1].with_traceback(exc_info[2])finally:exc_info = None     # avoid dangling circular refelif 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 writeresult = application(environ, start_response)     # 调用 applicationtry:for data in result:if data:    # don't send headers until body appearswrite(data)if not headers_sent:write('')   # send headers now if body was emptyfinally:if hasattr(result, 'close'):result.close()

application 返回数据

被调用的 application 对象根据 environ 的内容完成业务逻辑,并返回数据给 server

  • 先调用start_response(),返回statusresponse_headers给 server 作为 HTTP 响应头部。这同时也是一个信号,告诉 server,要开始返回 HTTP 的 body 了
  • 然后,通过 return 返回一个可迭代对象作为 HTTP 响应内容,如果响应为空,可以返回None

这样 server 可以按照规定的 HTTP 报文格式顺序,先发送 HTTP 响应头部,然后再发送 HTTP 响应的内容。

WSGI 中间件

需要注意的是,有些应用可以同时扮演 WSGI 的两种角色/具有对应的功能,比如中间件(middleware)。这是运行在 server 与 application 之间的应用。

对于 server ,中间件是 application,而对于 application,中间件是 server。

可以生成environ, 定义start_response, 调用application对象。也可以执行业务逻辑,调用start_response,并通过return返回结果。server 获取结果,发送给客户。

中间件可以有多层,能够处理所有经过的requestresponse,比如检查、修改。

中间件的工作过程:

上图中最上面的三个彩色框表示角色,中间的白色框表示操作,操作的发生顺序按照1 ~ 5进行了排序,我们直接对着上图来说明middleware是如何工作的:

  1. Server 收到客户端的 HTTP 请求后,生成了environ_s,并且已经定义了start_response_s
  2. Server 调用Middlewareapplication对象,传递的参数是environ_sstart_response_s
  3. Middleware 会根据environ执行业务逻辑,生成environ_m,并且已经定义了start_response_m
  4. Middleware 决定调用 Application 的 application 对象,传递参数是environ_mstart_response_m。Application 的 application 对象处理完成后,会调用start_response_m并且返回结果给Middleware ,存放在result_m中。
  5. Middleware 处理result_m,然后生成result_s,接着调用start_response_s,并返回结果result_s给 Server 端。Server 端获取到result_s后就可以发送结果给客户端了。

web 框架 WSGI application 端代码

Pyramid

from pyramid.config import Configurator
from pyramid.response import Responsedef 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 Routerclass 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 = Falsedebug_routematch = Falsethreadlocal_manager = managerdef __init__(self, registry):q = registry.queryUtilityself.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_factoryself.orig_handle_request = self.handle_requestself.handle_request = tweens(self.handle_request, registry)self.root_policy = self.root_factory # b/w compatself.registry = registrysettings = registry.settingsif 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 = IRequestcontext = Noneroutes_mapper = self.routes_mapperdebug_routematch = self.debug_routematchadapters = registry.adaptershas_listeners = registry.has_listenersnotify = registry.notifylogger = self.loggerhas_listeners and notify(NewRequest(request))# find the root objectroot_factory = self.root_factoryif 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'] = matchattrs['matched_route'] = routeif 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_factoryroot = root_factory(request)attrs['root'] = root# find a contexttraverser = 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 callablecontext_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_inforaise HTTPNotFound(msg)return responsedef invoke_subrequest(self, request, use_tweens=False):registry = self.registryhas_listeners = self.registry.has_listenersnotify = self.registry.notifythreadlocals = {'registry':registry, 'request':request}manager = self.threadlocal_managermanager.push(threadlocals)request.registry = registryrequest.invoke_subrequest = self.invoke_subrequestif use_tweens:handle_request = self.handle_requestelse:handle_request = self.orig_handle_requesttry:try:extensions = self.request_extensionsif 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 responsefinally:if request.finished_callbacks:request._process_finished_callbacks()finally:manager.pop()def __call__(self, environ, start_response):       # 按照 WSGI 规范定义的 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`` andreturn 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 centralobject.  It is passed the name of the module or package of theapplication.  Once it is created it will act as a central registry forthe view functions, the URL rules, template configuration and much more.The name of the package is used to resolve resources from inside thepackage or the folder the module is contained in depending on if thepackage parameter resolves to an actual python package (a folder withan `__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 orin the `__init__.py` file of your package like this::from flask import Flaskapp = Flask(__name__).. admonition:: About the First ParameterThe idea of the first parameter is to give Flask an idea whatbelongs to your application.  This name is used to find resourceson the file system, can be used by extensions to improve debugginginformation and a lot more.So it's important what you provide there.  If you are using a singlemodule, `__name__` is always the correct value.  If you however areusing a package, it's usually recommended to hardcode the name ofyour 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__`, thanksto how resources are looked up.  However it will make debugging morepainful.  Certain extensions can make assumptions based on theimport name of your application.  For example the Flask-SQLAlchemyextension will look for the code in your application that triggeredan SQL query in debug mode.  If the import name is not properly setup, that debugging information is lost.  (For example it would onlypick 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 warnwarn(DeprecationWarning('static_path is now called ''static_url_path'), stacklevel=2)static_url_path = static_pathif static_url_path is not None:self.static_url_path = static_url_pathif static_folder is not None:self.static_folder = static_folderif 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_pathself.config = self.make_config(instance_relative_config)# Prepare the deferred setup of the logger.self._logger = Noneself.logger_name = self.import_nameself.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 = Falseself._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 realresponse 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 thestring as body:class:`unicode`        a response object is created with thestring encoded to utf-8 as bodya WSGI function         the function is called as WSGI applicationand buffered as response object:class:`tuple`          A tuple in the form ``(response, status,headers)`` where `response` is any of thetypes defined here, `status` is a stringor an integer and `headers` is a list ofa dictionary with header values.======================= ===========================================:param rv: the return value from the view function.. versionchanged:: 0.9Previously a tuple was interpreted as the arguments for theresponse object."""status = headers = Noneif 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 = Noneelse:rv = self.response_class.force_type(rv, request.environ)if status is not None:if isinstance(status, string_types):rv.status = statuselse:rv.status_code = statusif headers:rv.headers.extend(headers)return rvdef wsgi_app(self, environ, start_response):       # 按照 WSGI 规范定义的 application"""The actual WSGI application.  This is not implemented in`__call__` so that middlewares can be applied without losing areference 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 andcan continue to call methods on it... versionchanged:: 0.7The behavior of the before and after request callbacks was changedunder error conditions and a new callback was added that willalways execute at the end of the request, independent on if anerror 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 optionalexception context to start the response"""ctx = self.request_context(environ)ctx.push()error = Nonetry:try:response = self.full_dispatch_request()except Exception as e:error = eresponse = self.make_response(self.handle_exception(e))return response(environ, start_response)finally:if self.should_ignore_error(error):error = Nonectx.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"这个变量没有设置,默认 wsgi.py 设置为 mysite.settings, mysite 是你的项目的名称。这是默认情况下, runserver 发现配置的地方。
'''# 应用 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 WSGIHandlerdef get_wsgi_application():"""The public interface to Django's WSGI support. Should return a WSGIcallable.Allows us to avoid making django.core.handlers.WSGIHandler public API, incase 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 = WSGIRequestdef __call__(self, environ, start_response):       # 按照 WSGI 规范定义的 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 gotself._request_middleware = Noneraiseset_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

参考资料

  • WSGI简介
  • PEP-3333

作者:超net
链接:https://www.jianshu.com/p/34ee01d85b0a
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

学习:
https://blog.csdn.net/tycoon1988/article/details/39961539

Python之Wsgi相关推荐

  1. Python的WSGI

    WSGI不是框架不是模块,仅仅是一个规范协议,定义了一些接口,却影响着Python网络开发的方方面面.对于WSGI有这么一段定义:WSGI is the Web Server Gateway Inte ...

  2. Python——eventlet.wsgi

    eventlet 的 wsgi 模块提供了一种启动事件驱动的WSGI服务器的简洁手段,可以将其作为某个应用的嵌入web服务器,或作为成熟的web服务器,一个这样的web服务器的例子就是 Spawnin ...

  3. Python Web初学解惑之 WSGI、flup、fastcgi、web.py的关系

    首先声明这篇文章 是我从 豆瓣 上面看到的. 原文地址 http://www.douban.com/note/13508388/?start=0&post=ok#last    看我之后 豁然 ...

  4. Python WSGI笔记

    Python WSGI笔记 本文转载自花了两个星期,我终于把 WSGI 整明白了 问1:一个HTTP请求到达对应的 application 处理函数要经过怎样的过程? 问2:如何不通过流行的 web ...

  5. python环境设置_CentOS 7.2环境搭建实录(第四章:python环境配置)

    第四章:python环境配置 使用环境工具 python 环境工具 python 2.7.5 # python2版本,系统自带 pip 9.0.1 # python2版本的pip,python工具集, ...

  6. python重复执行_python flask schedule重复运行 任务被重复执行问题 解决方案

    [注意:此文章为博主原创文章!转载需注意,请带原文链接,至少也要是txt格式!] 注意注意注意,先暂时抛开任务不谈!看下面的代码,这样有助于你快速定位你的问题点: #!/usr/bin/env pyt ...

  7. Python 编程系列

    目录 文章目录 目录 入门 进阶 模块 入门 <Python 是一门怎样的语言> <Python 基础 - Python 编程特色> <Python 基础 - 同时安装 ...

  8. Web 开发规范 — WSGI

    目录 目录 WSGI 简介 为什么需要 WSGI 这个规范 WSGI 如何工作 WSGI的角色 Server 如何调用 Application application 的两个参数 applicatio ...

  9. python如何开发小软件-Python程序员,如何快速开发一个小程序

    要点: 小程序是前后端分离的. 前端使用的是微信自定义的一套规范wxml+wxss+json+js,本质还是html+css+js. 后台可以选用任何你熟悉的语言:Java,Python,PHP,Ru ...

最新文章

  1. 写注册机犯法吗_逼着一个受害者去向另一个受害者道歉,不过分吗?
  2. leetcode 2 Add two numbers
  3. 怎样封装一个自己的mvc框架(七)
  4. 63. Unique Paths II 不同路径 II
  5. JSP里的System.out.println
  6. Java IO(二)——RandomAccessFile
  7. [deviceone开发]-心形点赞动画示例
  8. Android开发笔记(六十八)工程库打包
  9. 多线程模拟实现生产者/消费者模型 (借鉴)
  10. 华为STP相关功能配置
  11. [Linux]Linux man命令的使用方法
  12. Linux线程间死锁分析
  13. 维度模型数据仓库基础对象概念一览
  14. 万能声卡驱动win10_ASIO驱动(多通道版)-无驱外置USB声卡电音驱动
  15. 微信小号来了!同一个手机号可注册两个微信号
  16. Python分析《武林外传》 -----转载
  17. sublime3编程c语言,Sublime Text 3 实现C语言代码的编译和运行(示例讲解)
  18. ETW绕过PoC测试1--关闭你的ProcMon.exe
  19. 苹果mac休眠快捷键_「苹果电脑技巧」MAC快捷键(2018更新版)
  20. 商户注册和资质申请的业务流程

热门文章

  1. 产品外观设计是否应该创新?
  2. 如何使用iPhone测量距离
  3. 定向越野(添加任务信息和根据坐标位置触发游戏)
  4. 瑞星杀毒软件2008(完全免费) 20.21.22
  5. 皇家骑士300自走棋怎么用电脑玩 皇家骑士300自走棋模拟器教程
  6. 11-18我的编码八年抗战史
  7. [Operating.System.Concepts(9th,2012.12)].Abraham.Silberschatz.文字版(恐龙书——操作系统概念 原书第九版)课后习题 参考答案
  8. 这30个生活小技巧,不看后悔死……
  9. 06-(俏皮话)搞笑语录
  10. Unity 5.3中的GGX着色器