Web应用,wsgiref

Web应用组成

Web应用程序是基于B/S架构的,其中B指的是浏览器,负责向S端发送请求信息,而S端会根据接收到的请求信息返回相应的数据给浏览器,需要强调的一点是:S端由server和application两大部分构成

Web应用组成

开发Web应用

我们无需开发浏览器(本质即套接字客户端),只需要开发S端即可,S端的本质就是用套接字实现的

  • server端
import socket

def make_server(ip, port, app):  # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        # 1、接收浏览器发来的请求信息
        recv_data = conn.recv(1024)
        # print(recv_data.decode('utf-8'))

        # 2、将请求信息直接转交给application
        res = application(recv_data)

        # 3、向浏览器返回消息(此处并没有按照http协议返回)
        conn.send(res)
        
        conn.close()

def application(environ):  # 代表application
    # 处理业务逻辑
    return b'hello world'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)  
    # 在客户端浏览器输入:http://127.0.0.1:8008 会报错(注意:请使用谷歌浏览器)

目前S端已经可以正常接收浏览器发来的请求消息了,但是浏览器在接收到S端回复的响应消息b'hello world'时却无法正常解析 ,因为浏览器与S端之间收发消息默认使用的应用层协议是HTTP,浏览器默认会按照HTTP协议规定的格式发消息,而S端也必须按照HTTP协议的格式回消息才行

HTTP协议详解链接地址 https://blog.csdn.net/Waller_/article/details/104155456

S端修订版本:处理HTTP协议的请求消息,并按照HTTP协议的格式回复消息

import socket


def make_server(ip, port, app):  # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' % (ip, port))
    while True:
        conn, addr = sock.accept()

        # 1、接收并处理浏览器发来的请求信息
        # 1.1 接收浏览器发来的http协议的消息
        recv_data = conn.recv(1024)

        # 1.2 对http协议的消息加以处理,简单示范如下
        ll = recv_data.decode('utf-8').split('\r\n')
        head_ll = ll[0].split(' ')  # 将请求首行切分
        environ = {}
        environ['PATH_INFO'] = head_ll[1]  # 拿到PATH
        print(head_ll[1])  # /
        environ['method'] = head_ll[0]  # 拿到请求方法
        print(head_ll[0])  # GET

        # 2:将请求信息处理后的结果environ交给application,这样application便无需再关注请求信息的处理,可以更加专注于业务逻辑的处理
        res = app(environ)

        # 3:按照http协议向浏览器返回消息
        # 3.1 返回响应首行
        conn.send(b'HTTP/1.1 200 OK\r\n')
        # 3.2 返回响应头(可以省略)
        conn.send(b'Content-Type: text/html\r\n\r\n')
        # 3.3 返回响应体
        conn.send(res)

        conn.close()


def app(environ):  # 代表application
    # 处理业务逻辑
    return b'hello world'


if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)

重启S端后,再在客户端浏览器输入:http://127.0.0.1:8008 便可以看到正常结果hello world了。

我们不仅可以回复hello world这样的普通字符,还可以夹杂html标签,浏览器在接收到消息后会对解析出的html标签加以渲染

import socket

def make_server(ip, port, app): 
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()
        
        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('\r\n')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OK\r\n')
        conn.send(b'Content-Type: text/html\r\n\r\n')
        conn.send(res)

        conn.close()

def app(environ):
    # 返回html标签
    return b'<h1>hello web</h1><img src="https://www.baidu.com/img/bd_logo1.png"></img>'

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)

注意: HTTP响应的Header中有一个 `Content-Type`表明响应的内容格式。如 `text/html`表示HTML网页。

更进一步我们还可以返回一个文件,例如timer.html,内容如下

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>{{ time }}</h2>
</body>
</html>

S端程序如下

import socket

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('\r\n')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OK\r\n')
        conn.send(b'Content-Type: text/html\r\n\r\n')
        conn.send(res)

        conn.close()

def app(environ):
    # 处理业务逻辑:打开文件,读取文件内容并返回
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()
    return data.encode('utf-8')

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app)

上述S端为浏览器返回的都是静态页面(内容都固定的),我们还可以返回动态页面(内容是变化的)

import socket

def make_server(ip, port, app): # 代表server
    sock = socket.socket()
    sock.bind((ip, port))
    sock.listen(5)
    print('Starting development server at http://%s:%s/' %(ip,port))
    while True:
        conn, addr = sock.accept()

        recv_data = conn.recv(1024)
        ll=recv_data.decode('utf-8').split('\r\n')
        head_ll=ll[0].split(' ')
        environ={}
        environ['PATH_INFO']=head_ll[1]
        environ['method']=head_ll[0]

        res = app(environ)

        conn.send(b'HTTP/1.1 200 OK\r\n')
        conn.send(b'Content-Type: text/html\r\n\r\n')
        conn.send(res)

        conn.close()

def app(environ):
    # 处理业务逻辑
    with open('timer.html', 'r', encoding='utf-8') as f:
        data = f.read()

    import time
    now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
    data = data.replace('{{ time }}', now)  # 字符串替换
    return data.encode('utf-8')

if __name__ == '__main__':
    make_server('127.0.0.1', 8008, app) # 在浏览器输入http://127.0.0.1:8008,每次刷新都会看到不同的时间

Web框架的由来

综上案例我们可以发现一个规律,在开发S端时,server的功能是复杂且固定的(处理socket消息的收发和http协议的处理),而app中的业务逻辑却各不相同(不同的软件就应该有不同的业务逻辑),重复开发复杂且固定的server是毫无意义的,有一个wsgiref模块帮我们写好了server的功能,这样我们便只需要专注于app功能的编写即可

wsgiref

wsgiref实现了server,即make_server

from wsgiref.simple_server import make_server
# wsgiref 将socket封装好了
​
​
def run(env, response):
    '''
    env 是请求相关的数据
    response 是响应相关的数据
    '''
    print(env)  # 收到的是一个字典,里面的 键 PATN_INFO 对应的值就是客户端url的请求后缀
    response('200 OK', [])
    return [b'hello Django']
​
​
if __name__ == '__main__':
    server = make_server('127.0.0.1', 8080, run)
    # 实时监测127.0.0.1:8080地址,一旦有客户端连接,会自动加括号调用run方法
    server.serve_forever()  # 启动服务端

根据不同的路径返回不同的内容

from wsgiref.simple_server import make_server
# wsgiref 将socket封装好了
​
​
def index(env):
    return 'index'
​
def login(env):
    return 'login'
​
def errors(env):
    return '404 error'
​
urls = [
    ('/index', index),
    ('/login', login),
]
​
def run(env, response):
​
    response('200 OK', [])
    current_path = env.get('PATH_INFO')# 拿到的是URL后缀
​
    # 定义一个存储函数的标志位
    func = None
    for url in urls:
        # 判断当前请求的url是否在元组内
        if url[0] == current_path:
            # 若在,赋值给func
            func = url[1]
            # 一旦匹配上就退出循环,节省资源
            break
    # 判断func是否有值
    if func:
        # 调用
        res = func(env)  # env是个大字典,里面含有信息,在后续的逻辑中可能还需要用到字典内的信息
    else:
        res = errors(env)
    return [res.encode('utf-8')]  # 将所有逻辑函数返回的字符串在此统一编码,发给客户端
​
​
if __name__ == '__main__':
    server = make_server('127.0.0.1', 8080, run)
    # 实时监测127.0.0.1:8080地址,一旦有客户端连接,会自动加括号调用run方法
    server.serve_forever()  # 启动服务端

随着业务逻辑复杂度的增加,处理业务逻辑的函数以及url_patterns中的映射关系都会不断地增多,此时仍然把所有代码都放到一个文件中,程序的可读性和可扩展性都会变得非常差,所以我们应该将现有的代码拆分到不同文件中

基于wsgiref模块将代码拆分成urls.py路由文件,views.py视图文件和wsgiref通信文件三部分,后续加功能只需要在urls.py文件和view.py文件中加入对应功能即可

project

mysite # 文件夹
    ├── app01 # 文件夹
    │   └── views.py
    ├── mysite # 文件夹
    │   └── urls.py
    └── templates # 文件夹
    │   ├── index.html
    │   └── timer.html
    ├── main.py
  • views.py 内容如下
def index(env):
    return 'index'
​
def login(env):
    return 'login'
​
def errors(env):
    return '404 error'
  • urls.py内容如下:
# 路径跟函数的映射关系
from app01.views import * # 需要导入views中的函数
urls = [
    ('/index', index),
    ('/login', login),
]
  • main.py 内容如下:
from wsgiref.simple_server import make_server
from app01.urls import urls  # 需要导入urls中的urls

def run(env, response):
​
    response('200 OK', [])
    current_path = env.get('PATH_INFO')# 拿到的是URL后缀
​
    # 定义一个存储函数的标志位
    func = None
    for url in urls:
        # 判断当前请求的url是否在元组内
        if url[0] == current_path:
            # 若在,赋值给func
            func = url[1]
            # 一旦匹配上就退出循环,节省资源
            break
    # 判断func是否有值
    if func:
        # 调用
        res = func(env)  # env是个大字典,里面含有信息,在后续的逻辑中可能还需要用到字典内的信息
    else:
        res = errors(env)
    return [res.encode('utf-8')]  # 将所有逻辑函数返回的字符串在此统一编码,发给客户端
​
​
if __name__ == '__main__':
    server = make_server('127.0.0.1', 8080, run)
    # 实时监测127.0.0.1:8080地址,一旦有客户端连接,会自动加括号调用run方法
    server.serve_forever()  # 启动服务端

至此,我们就针对application的开发自定义了一个框架,所以说框架的本质就是一系列功能的集合体、不同的功能放到不同的文件中。有了该框架,可以让我们专注于业务逻辑的编写,极大的提高了开发web应用的效率(开发web应用的框架可以简称为web框架)

比如我们新增一个业务逻辑,要求为:浏览器输入http://127.0.0.1:8011/home 就能访问到home.html页面,在框架的基础上具体开发步骤如下:

步骤一:在templates文件夹下新增home.html

步骤二:在urls.py的url_patterns中新增一条映射关系

urls = [
    ('/index', index),
    ('/login', login),
    ('/home', home),
]

步骤三:在views.py中新增一个名为home的函数

def home(environ):
    with open('templates/home.html', 'r',encoding='utf-8') as f: 
        data = f.read()
    return data.encode('utf-8')

我们自定义的框架功能有限,在Python中我们可以使用别人开发的、功能更强大的Django框架

其他: django源码之启动wsgi发生了那些事https://www.cnblogs.com/xiaoyuanqujing/articles/11902303.html

推荐文章 https://www.jianshu.com/p/c66d3adeaaed

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用web框架可以帮助我们快速构建Web应用程序。以下是一些常见的web框架和如何使用它们: 1. Flask:Flask是一个轻量级的Python web框架,它非常适合小型应用程序。您可以使用以下命令安装Flask: ``` pip install flask ``` 在您的项目中导入Flask: ```python from flask import Flask ``` 创建一个Flask应用程序并运行它: ```python app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run() ``` 2. Django:Django是一个全功能的Python web框架,它适合中型到大型应用程序。您可以使用以下命令安装Django: ``` pip install django ``` 在您的项目中创建一个Django应用程序: ```python python manage.py startapp myapp ``` 编写视图函数和URLconf: ```python from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), ] ``` 在您的项目中运行Django服务器: ```python python manage.py runserver ``` 3. Pyramid:Pyramid是一个灵活的Python web框架,它适合中型到大型应用程序。您可以使用以下命令安装Pyramid: ``` pip install pyramid ``` 在您的项目中创建一个Pyramid应用程序: ```python from wsgiref.simple_server import make_server from pyramid.config import Configurator from pyramid.response import Response def hello_world(request): return Response('Hello, World!') if __name__ == '__main__': with Configurator() as config: config.add_route('hello', '/') config.add_view(hello_world, route_name='hello') app = config.make_wsgi_app() server = make_server('localhost', 8080, app) server.serve_forever() ``` 这些是三个常见的Python web框架的用法示例。使用这些框架可以帮助您快速开发Web应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值