wsgi.py的详解

WSGI是Python web开发中的标准接口,定义了服务器程序与应用程序的交互方式。本文详细解释了WSGI应用程序的规定,包括可调用对象的类型和参数,以及WSGI服务器程序如何调用应用程序。Middleware作为两者之间的桥梁,使得服务器能够根据需求处理不同的URL。通过示例和源码分析,深入理解了environ变量和start_response函数的用途,以及Middleware在实际工作中的应用。

WSGI的理解

Python web开发中,服务端程序可分为2个部分:

  1. 服务器程序(用来接收、整理客户端发送的请求)
  2. 应用程序(处理服务器程序传递过来的请求)
    在开发应用程序的时候,我们会把常用的功能封装起来,成为各种框架,比如Flask,Django,Tornado(使用某框架进行web开发,相当于开发服务端的应用程序,处理后台逻辑)
    但是,服务器程序和应用程序互相配合才能给用户提供服务,而不同应用程序(不同框架)会有不同的函数、功能。 此时,我们就需要一个标准,让服务器程序和应用程序都支持这个标准,那么,二者就能很好的配合了

    WSGI:wsgi是python web开发的标准,类似于协议。它是服务器程序和应用程序的一个约定,规定了各自使用的接口和功能,以便二和互相配合

WSGI应用程序的部分规定

  1. 应用程序是一个可调用的对象
    可调用的对象有三种:
    1. 一个函数
    2. 一个类,必须实现__call__()方法
    3. 一个类的实例
  2. 这个对象接收两个参数
    从源码中,我们可以看到,这两个参数是environ, start_response. 以可调用对象为一个类为例:

    class application:
        def __call__(self, environ, start_response):
            pass
  3. 可调用对象需要返回一个可迭代的值。以可调用对象为一个类为例:

    class application:
        def __call__(self, environ, start_response):
            return [xxx]

WSGI服务器程序的部分规定

  1. 服务器程序需要调用应用程序

    def run(application):     #服务器程序调用应用程序
        environ = {}     #设定参数
    
        def start_response(xxx):     #设定参数
            pass
        result = application(environ, start_response)          #调用应用程序的__call__函数(这里应用程序是一个类)
        def write(data):
                   pass
        def data in result:          #迭代访问
            write(data)
    服务器程序主要做了以下的事:
         1. 设定应用程序所需要的参数
         2. 调用应用程序
         3. 迭代访问应用程序的返回结果,并传给客户端 

Middleware

middleware是介于服务器程序和应用程序中间的部分,middleware对服务器程序和应用程序是透明的。

对于服务器程序来说,middleware就是应用程序,middleware需要伪装成应用程序,传递给服务器程序
对于应用程序来说,middleware就是服务器程序,middleware需要伪装成服务器程序,接受并调用应用程序

服务器程序获取到了客户端请求的URL,需要把URL交给不同的函数处理,这个功能可以使用middleware实现:

# URL Routing middleware
def urlrouting(url_app_mapping):   
     def midware_app(environ, start_response):       #函数可调用,包含2个参数,返回可迭代的值
          url = environ['PATH_INFO']       
          app = url_app_mapping[url]       #获得对应url的应用程序
          result = app(environ, start_response)       #调用应用程序
          return result       
     return midware_app

函数midware_app就是middleware:
一方面,midware_app函数设置了应用程序所需要的变量,并调用了应用程序。所以对于应用程序来说,它是一个服务器程序
另一方面,midware_app函数是一个可调用的对象,接收两个参数,同时可调用对象返回了一个可迭代的值。所以对于服务器程序来说,它是一个应用程序

写中间件(middleware)的逻辑:
1. middleware需要伪装成应用程序—> WSGI应用程序的要求 —> 1. 可调用  2. 两个参数  3. 返回可迭代的值
2. middleware需要伪装成服务器程序 —> WSGI服务器程序的要求 —> 调用应用程序

我们需要了解一下environ这个变量。在WSGI中, 应用程序需要两个参数:environstart_response, 在服务器程序调用应用程序之前, 需要先设定这两个参数。 其中start_response通常是个可调用的方法, 而environ则是一个字典, 它是在CGI中定义的, 查看CGI文档The Common Gateway Interface Specification, 可以找到关于environ的定义。
以下是environ中的参数:

  AUTH_TYPE
  CONTENT_LENGTH        #HTTP请求中Content-Length的部分
  CONTENT_TYPE          #HTTP请求中Content-Tpye的部分
  GATEWAY_INTERFACE
  HTTP_*                    #包含一系列变量, 如HTTP_HOST,HTTP_ACCEPT等
  PATH_INFO             #URL路径除了起始部分后的剩余部分,用于找到相应的应用程序对象,如果请求的路径就是根路径,这个值为空字符串
  PATH_TRANSLATED
  QUERY_STRING          #URL路径中?后面的部分
  REMOTE_ADDR
  REMOTE_HOST
  REMOTE_IDENT
  REMOTE_USER
  REQUEST_METHOD        #HTTP 请求方法,例如 "GET", "POST"
  SCRIPT_NAME           #URL路径的起始部分对应的应用程序对象,如果应用程序对象对应服务器的根,那么这个值可以为空字符串
  SERVER_NAME
  SERVER_PORT
  SERVER_PROTOCOL       #客户端请求的协议(HTTP/1.1 HTTP/1.0)
  SERVER_SOFTWARE

举例:http://localhost:5000/aaa?666, 变量值为:

REQUEST_METHOD=‘GET’
SCRIPT_NAME=''
SERVER_NAME='localhost'
SERVER_PORT=‘5000PATH_INFO='/aaa'
QUERY_STRING='666'
SERVER_PROTOCOL='HTTP/1.1'
CONTENT_TYPE='text/plain'
CONTEN_LENGTH='' 

HTTP_HOST = 'localhost:8000'
HTTP_ACCEPT = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8'
HTTP_ACCEPT_ENCODING = 'gzip,deflate,sdch'
HTTP_ACCEPT_LANGUAGE = 'en-US,en;q=0.8,zh;q=0.6,zh-CN;q=0.4,zh-TW;q=0.2'
HTTP_CONNECTION = 'keep-alive'
HTTP_USER_AGENT = 'Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36'

对于start_response()函数:
start_response是HTTP响应的开始, 它的形式为:start_response(status, response_headers, exc_info=None)
status表示HTTP状态码, 比如200 OK
response_headers是一个列表,列表元素是个tuple:(header_name, header_value)
exc_info是个可选参数, 当处理请求的过程中发生错误时, 会设置该参数, 同时会调用start_response
举一个werkzeug官方文档上的例子,我稍作改进,以进行分析(这段建议看完wsgi.py第二部分的SharedDataMiddleware类再看):

class Shortly(object):

    def __init__(self, config):
        self.redis = redis.Redis(config['redis_host'], config['redis_port'])

    def dispatch_request(self, request):
        return Response('Hello World!')     #初始化Response类

    def wsgi_app(self, environ, start_response):
        request = Request(environ)
        response = self.dispatch_request(request)   #response的类型为Response类
        print('%%%%%%%%%%%%%%%%%%%%%%%%%%%')
        return response(environ, start_response)   #Response的__call__函数就是应用程序,返回迭代对象

    def __call__(self, environ, start_response):
        print(self.wsgi_app)
        print('erghrgheoghegoierge')
        return self. wsgi_app(environ, start_response)


def create_app(redis_host='localhost', redis_port=6379, with_static=True):
    app = Shortly({
        'redis_host':       redis_host,
        'redis_port':       redis_port
    })
    if with_static:
        print('yes')
        app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
            '/static':  os.path.join(os.path.dirname(__file__), 'static')
        })
    print('33333333333333333')
    return app
#开启本地服务器
if __name__ == '__main__':
    from werkzeug.serving import run_simple
    app = create_app()  #创建应用程序的实例
    run_simple('127.0.0.1', 5000, app, use_debugger=True, use_reloader=True)

我们查看Response的源码(Response继承了BaseResponse,查看BaseResponse的源码即可)可以知道:函数dispatch_request()返回的值是Request的构造函数,即返回了一个Response类, 在函数wsgi_app()中,request的值的类型就是Response, 所以wsgi_app()的返回值response(environ, start_response)实际上是调用了Response类的__call__()函数。
看了源码我们可以发现,__call__()是一个WSGI的应用程序!

当运行这个程序的时候:

22222222222222222
yes
33333333333333333
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
 * Restarting with stat
22222222222222222
yes
33333333333333333   

我们先不纠结为什么读取了2次。
当我们打开这个网页,控制台的输出为:

<werkzeug.wsgi.SharedDataMiddleware object at 0x1007be7b8>    #说明wsgi_app是SharedDataMiddleware的实例!  
erghrgheoghegoierge
%%%%%%%%%%%%%%%%%%%%%%%%%%%         #说明执行了原wsgi_app函数中的内容!
127.0.0.1 - - [22/May/2015 21:01:25] "GET / HTTP/1.1" 200 -

可以注意到,在本例中,app.wsgi_app这个方法已经变成了一个SharedDataMiddleware类的实例,我很好奇当服务器把environ和start_response传递给app后,为什么wsgi_pp还会执行原wsgi_app中的内容呢?
当我们访问主机地址的时候,服务器程序接受用户请求,然后会把environ和start_response传递给应用程序app,app会执行__call__函数,在该函数中,会执行app.wsgi_app这个函数。 然后wsgi_app会执行ShareDataMiddleware__call__()函数
这地方需要我们看SharedDataMiddleware类的__call__()的源码。看了源码我们可以发现,由于用户没有请求静态文件,所以会执行return self.app(environ, start_response),在本例中,我们可以看到在create_app()中,我们定义的ShareDataMiddleware的应用程序是app.wsgi_app,所以这里返回的是原wsgi_app函数!所以当然会执行原函数了~
同时,我们也可以在static文件夹中放一个文件,然后访问试一下:127.0.0.1/static/文件名,此时就能看到那个文件了!
通过本例,我们可以更深刻的了解Middleware的作用:

Middleware介于服务器程序和应用程序之间,它会接收服务器发来的消息(environ和
start_response),并做一定的处理,然后把需要应用程序处理的部分传递给应用程序处理

另外, 服务器程序还需要定义WSGI的相关变量:

wsgi.version                                                  
值的形式为 (1, 0) 表示 WSGI 版本 1.0                             
wsgi.url_scheme                                               
表示 url 的模式,例如 "https" 还是 "http"                        
wsgi.input                                                    
输入流,HTTP请求的 body 部分可以从这里读取                         
wsgi.erros                                                    
输出流,如果出现错误,可以写往这里
wsgi.multithread
如果应用程序对象可以被同一进程中的另一线程同时调用,这个值为True
wsgi.multiprocess
如果应用程序对象可以同时被另一个进程调用,这个值为True
wsgi.run_once
如果服务器希望应用程序对象在包含它的进程中只被调用一次,那么这个值为True
<think>我们参考标准的Django项目结构,并按照用户要求创建人员管理系统的项目结构。用户要求包含:-manage.py-主应用目录person_system(包含models.py,views.py,urls.py,templates子目录)-项目配置目录project(包含settings.py,urls.py)具体步骤如下:1.**创建项目**:使用django-admin命令创建项目,项目名为project,此时会生成project目录和manage.py。2.**创建应用**:在项目根目录下,使用manage.py创建应用person_system。3.**调整目录结构**:在person_system应用目录下创建templates目录,并在templates下再创建person_system子目录(这是Django模板查找的约定,避免不同应用模板重名)。4.**配置项目settings.py**:将person_system应用添加到INSTALLED_APPS中,并配置数据库、时区等。5.**配置项目urls.py**:包含应用person_system的urls。6.**创建应用内部文件**:在person_system下创建models.py、views.py、urls.py。具体目录结构如下(参考用户提供的目录结构[^3]和标准Django项目结构):```person_management/#项目根目录├──manage.py├──person_system/#应用目录│├──__init__.py│├──admin.py│├──apps.py│├──models.py│├──tests.py│├──urls.py#应用的路由配置│├──views.py#应用的视图│└──templates/#模板目录│└──person_system/#应用模板目录│├──list.html│├──form.html│└──import.html└──project/#项目配置目录├──__init__.py├──settings.py#项目设置├──urls.py#项目根路由└──wsgi.py```###创建步骤详解####1.创建项目在命令行中执行(确保已安装Django):```bashdjango-adminstartprojectperson_management```此时会生成如下结构:```person_management/├──manage.py└──person_management/├──__init__.py├──settings.py├──urls.py└──wsgi.py```注意:我们通常把内层`person_management`目录重命名为`project`(或者一开始创建时可以直接命名为project,但为了与用户要求一致,我们这里使用重命名)。所以我们将内层目录改名为`project`,同时调整`manage.py`文件中的配置(将`os.environ.setdefault('DJANGO_SETTINGS_MODULE','person_management.settings')`改为`project.settings`)?实际上,如果项目名和目录名一致,则无需修改。但用户要求项目配置目录名为`project`,所以我们将内层目录重命名为`project`。但是,为了减少麻烦,我们在创建项目的时候可以直接将项目命名为`project`,但这样外层目录就是`project`,而用户要求的主目录可能是任意名称(比如`person_management`)。所以,我们这样做:-创建项目:`django-adminstartprojectproject.`(注意最后有一个点,表示在当前目录创建,这样就会生成manage.py和project目录)-或者,先创建目录`person_management`,进入该目录,然后执行`django-adminstartprojectproject.`,这样就在`person_management`目录下生成manage.py和project目录。推荐方式:```bashmkdirperson_managementcdperson_managementdjango-adminstartprojectproject.```此时目录结构为:```person_management/├──manage.py└──project/├──__init__.py├──settings.py├──urls.py└──wsgi.py```####2.创建应用在项目根目录(即manage.py所在目录)执行:```bashpythonmanage.pystartappperson_system```这样就会生成`person_system`应用目录,结构如下:```person_management/├──manage.py├──person_system/│├──__init__.py│├──admin.py│├──apps.py│├──models.py│├──tests.py│├──views.py└──project/......```####3.创建模板目录在`person_system`应用目录下创建:```person_system/└──templates/└──person_system/```将模板文件(list.html,form.html,import.html)放在`person_system/templates/person_system/`下。####4.配置项目编辑`project/settings.py`:-将`person_system`添加到`INSTALLED_APPS`中:```pythonINSTALLED_APPS=[...,'person_system',]```####5.配置项目URL编辑`project/urls.py`,将应用的路由包含进来:```pythonfromdjango.contribimportadminfromdjango.urlsimportpath,includeurlpatterns=[path('admin/',admin.site.urls),path('',include('person_system.urls')),#包含person_system应用的路由]```####6.创建应用URL配置在`person_system`目录下新建`urls.py`,内容参考之前提供的URL配置。####7.编写模型、视图、模板按照之前提供的代码示例,编写models.py、views.py以及模板文件。####8.数据库迁移生成迁移文件并迁移:```bashpythonmanage.pymakemigrationspythonmanage.pymigrate```注意:如果迁移过程中遇到类似引用[4]中的错误,可能是数据库版本问题,需要检查settings.py中的数据库配置和SQL语法。###PyCharm中的操作在PyCharm中可以直接创建Django项目:1.新建项目,选择Django,设置项目位置为`person_management`。2.在项目设置中,应用名称填写`person_system`(也可以后续用命令添加应用)。3.创建项目后,PyCharm会自动生成项目结构和manage.py。###注意事项-如果使用MySQL数据库,需要在settings.py中配置,并安装mysqlclient。-在视图中使用openpyxl处理Excel,需要安装:`pipinstallopenpyxl`###相关问题1.如何解决Django数据库迁移时出现的MySQL语法错误(如引用[4]中的错误)?2.Django中如何使用多个数据库?3.如何为Django项目配置静态文件(CSS,JS)?4.如何在PyCharm中调试Django视图函数?</think> ### Charm Python Web 开发中人员管理系统项目结构创建指南 在 Charm Python Web 开发(指 PyCharm IDE 中的 Django Web 开发)中创建人员管理系统的标准项目结构如下,结合了 Django 最佳实践和您的需求: #### 完整项目结构(使用树状图展示) ``` person_management/ # 项目根目录 ├── manage.py # Django 管理脚本 ├── person_system/ # 应用目录(主应用) │ ├── __init__.py │ ├── admin.py # 管理员配置 │ ├── apps.py # 应用配置 │ ├── migrations/ # 数据库迁移文件 │ │ └── __init__.py │ ├── models.py # 数据模型定义 │ ├── tests.py # 单元测试 │ ├── views.py # 视图函数 │ ├── urls.py # 应用路由配置 │ └── templates/ # 模板目录 │ └── person_system/ # 应用专属模板 │ ├── list.html # 人员列表页 │ ├── form.html # 表单页 │ └── import.html # 数据导入页 └── project/ # 项目配置目录 ├── __init__.py ├── asgi.py # ASGI 配置 ├── settings.py # 项目设置 ├── urls.py # 主路由配置 └── wsgi.py # WSGI 配置 ``` #### 创建步骤详解(在 PyCharm 中操作) 1. **创建 Django 项目** ```bash # PyCharm 终端执行(或使用 IDE 的 Django 项目向导) django-admin startproject person_management . ``` 这将创建包含 `manage.py` 和 `project/` 配置目录的基础结构[^2] 2. **创建主应用** ```bash python manage.py startapp person_system ``` 3. **配置项目设置 (`project/settings.py`)** ```python # 添加应用到 INSTALLED_APPS INSTALLED_APPS = [ ... 'person_system', ] # 配置模板目录 TEMPLATES = [ { 'DIRS': [os.path.join(BASE_DIR, 'person_system/templates')], ... } ] ``` 4. **配置主路由 (`project/urls.py`)** ```python from django.urls import include, path urlpatterns = [ path('', include('person_system.urls')), ] ``` 5. **创建应用路由 (`person_system/urls.py`)** ```python from django.urls import path from . import views urlpatterns = [ path('', views.person_list, name='person_list'), path('add/', views.person_add, name='person_add'), path('edit/<int:pid>/', views.person_edit, name='person_edit'), path('delete/<int:pid>/', views.person_delete, name='person_delete'), path('export/', views.export_to_xls, name='export_xls'), path('import/', views.import_from_xls, name='import_xls'), ] ``` 6. **创建模板目录结构** - 在 PyCharm 项目视图中右键点击 `person_system` - 新建目录 `templates/person_system` - 在目录内创建 HTML 模板文件 #### PyCharm 专属功能利用 1. **数据库工具** - 连接设置中的数据库(如 SQLite) - 直接查看 `Person` 模型对应的数据库表 2. **Django 控制台** - 使用内置终端执行迁移命令: ```bash python manage.py makemigrations python manage.py migrate ``` 3. **模板调试** - 在模板中设置断点 - 使用调试模式检查模板变量值 4. **运行配置** - 创建 Django server 运行配置 - 设置环境变量和启动参数 #### 常见问题解决方案(参考引用) - **迁移错误处理**[^4]:若遇到类似 `migrations.exceptions.MigrationSchemaMissing` 错误: 1. 确认数据库连接配置正确 2. 删除迁移目录(除 `__init__.py` 外所有文件) 3. 重新执行: ```bash python manage.py makemigrations python manage.py migrate ``` - **结构验证**[^3]:使用 PyCharm 的 `Inspect Code` 功能检查项目结构规范性 ### 项目结构优化建议 1. **静态文件管理**:添加 `static/` 目录存放 CSS/JS 2. **环境隔离**:创建 `requirements.txt` 记录依赖 ``` Django>=4.2 openpyxl ``` 3. **模块化开发**:对于大型系统,可创建子应用: ```bash python manage.py startapp reporting # 报表子系统 ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lm_y

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值