WSGI一Web 框架基础(一)

一:简介

WSGI ,全称是Web Server Gateway Interface (Web 服务器网关接口)。这是Python 中定义的一个网关协议,规定了Web Server 如何跟应用程序交互。Web Server 可以理解为一个Web 应用的容器,通过它可以启动应用,进而提供HTTP 服务。而应用程序是指我们基于框架所开发的系统。

这个协议最主要的目的就是保证在Python 中所有Web Server 程序或者说Gateway 程序,能够通过统一的协议跟Web 框架或者说Web 应用进行交互。这对于部署Web 程序来说很重要,你可以选择任何一个实现了WSGI 协议的Web Server 来跑你的程序。
如果没有这个协议, 那么每个程序、每个Web Server 可能都会实现各自的接口,实现各自的“轮子”, 最终的结果会是一团乱。

使用统一协议的另外一个好处: Web 应用框架只需要实现WSGI ,就可以跟外部请求进行交互了,不用去针对某个Web Server 来独立开发交互逻辑,开发者可以把精力放在框架本身。

理解这一协议非常重要,因为在Python中大部分的Web 框架都实现了此协议,也使用WSGI 容器来进行部署。

二:简单的Web Server

在了解WSGI 协议之前, 我们先来看一个通过socket 编程实现的Web 服务的代码。其逻辑很简单,就是通过监昕本地8000 端口,接收客户端发过来的数据,然后返回对应的HTTP 响应:

注:此处要知道HTTP请求/响应的数据有哪几部分组成、以及格式、HTTP协议数据格式

"""
代码需要在 Python 3
功能:该文件充当socket服务器、浏览器为客户端
"""
import socket

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
body = '<h1> 我是 HTTP Response HTTP Response HTTP Response<h1>'  # 响应体数据
response_params = [
    'HTTP/1.0 200 OK',
    'Date: Sat, 10 jun 2017 01:01:01 GMT',
    'Content-Type: text/html; charset=utf-8',
    'Content-Length: {}\r\n'.format(len(body)),
    body,
]
response = '\r\n'.join(response_params)  # 返回的HTTP数据要求

def handle_connection(conn, addr):
    """ 处理链接 """
    request = b""  # 网络数据只能是byte型的
    # 判断有没有发送HTTP请求、该过层就是点击打印出的url、HTTP请求有浏览器完成(GET请求)
    # 如没有、接受数据
    while EOL1 not in request and EOL2 not in request:
        request += conn.recv(1024)  # 接受数据
    print(request)  # 打印出请求数据
    conn.send(response.encode())  # 以HTTP格式的数据回复浏览器(客户端) #response 转为bytes 后传输
    conn.close()  # 关闭连接

def main():
    # socket.AF_INET 用于服务器与服务器之间的网络通信
    # socket.SOCK_STREAM 用于基于TCP 的流式socket通信
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化socket
    # 设置棕口可复用,保证我们每次按Ctrl + C组合键之后,快这主启
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serversocket.bind(('127.0.0.1', 8080))  # 绑定ip 与port
    serversocket.listen(10)  # 监听 设置backlog-socket 连接最大排队数量
    print('http://127.0.0.1:8080')  # 控制台带引url

    try:
        while True:
            conn, address = serversocket.accept()  # 等待客户端建立连接
            handle_connection(conn, address)  # 处理客户端请求
    finally:
        serversocket.close()  # 关闭服务

if __name__ == '__main__':
    main()

控制台输出:

客户端(浏览器)请求及输出结果:

理解这段代码很重要!这是Web 服务最基本的模型, 通过socket 和HTTP 协议提供Web 服务。

是不是感觉自己很牛逼、键值到达了人生的巅峰!可是这样的实现实际上是不足以满足实际达要求的!

三;:多线程版本

上面只是简单的单进程、单线程的版本,这能够让你更好地理解Web 服务。现在我们把逻辑变得复杂一些,调整上面的代码。

"""
代码需要在 Python 3
功能:该文件充当socket服务器、浏览器为客户端
"""
import socket
import time

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
body = '<h1> 我是 HTTP Response HTTP Response HTTP Response<h1>'  # 响应体数据
response_params = [
    'HTTP/1.0 200 OK',
    'Date: Sat, 10 jun 2017 01:01:01 GMT',
    'Content-Type: text/html; charset=utf-8',
    'Content-Length: {}\r\n'.format(len(body)),
    body,
]
response = '\r\n'.join(response_params)  # 返回的HTTP数据要求


def handle_connection(conn, addr):
    """ 处理链接 """
    print('来了新连接: ', conn, addr)
    time.sleep(60)
    request = b""  # 网络数据只能是byte型的
    # 判断有没有发送HTTP请求、该过层就是点击打印出的url、HTTP请求有浏览器完成(GET请求)
    # 如没有、接受数据
    while EOL1 not in request and EOL2 not in request:
        request += conn.recv(1024)  # 接受数据
    print(request)  # 打印出请求数据
    conn.send(response.encode())  # 以HTTP格式的数据回复浏览器(客户端) #response 转为bytes 后传输
    conn.close()  # 关闭连接


def main():
    # socket.AF_INET 用于服务器与服务器之间的网络通信
    # socket.SOCK_STREAM 用于基于TCP 的流式socket通信
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 实例化socket
    # 设置棕口可复用,保证我们每次按Ctrl + C组合键之后,快这主启
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serversocket.bind(('127.0.0.1', 8080))  # 绑定ip 与port
    serversocket.listen(10)  # 监听 设置backlog-socket 连接最大排队数量
    print('http://127.0.0.1:8080')  # 控制台带引url

    try:
        while True:
            conn, address = serversocket.accept()  # 等待客户端建立连接
            handle_connection(conn, address)  # 处理客户端请求
    finally:
        serversocket.close()  # 关闭服务


if __name__ == '__main__':
    main()

重新运行,然后访问页面,同时再打开-个(或者更多) 新的浏览器窗口(注意不是新的标签页),访问页面, 同时·观察Consol巳上的输出。看看有什么问题。
思考一下。有什么发现吗?

好了,问题是这样:当我们处于单进程、单线程模型时,程序接受一个请求, 然后需要花60 s 处理,此时新的请求是进不来的,因为只有一个处理程序。类比到生活中就是, 你去ATM上取钱,只有一台机器,前面如果有人需要花费100 s 才能完成操作, 那么你就要等100 s。到这里,你应该能明白最简单的模型只是用来理解原理的, 实用性并不强。如果我用上面的代码做了个网站, 每次只能有一个用户访问,那恐怕就没人来了。
所以,我们需要了解其他的Web 服务模型-一一多线程模式。

理论: 每来一个新请求,我们就创建一个线程来处理它, 这样的话,这个请求处理的耗时不会影响下一个请求。就好像是, 只要有人来取钱,就会自动出现一个ATM 一样。
多线程模式代码(需要再回忆Python 多线程部分的知识点)

import errno
import socket
import threading
import time

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
body = '<h1> 我是 HTTP Response HTTP Response HTTP Response<h1>'  # 响应体数据
response_params = [
    'HTTP/1.0 200 OK',
    'Date: Sun, 27 may 2019 01:01:01 GMT',
    'Content-Type: text/html; charset=utf-8',
    'Content-Length: {length}\r\n',
    body,
]
response = '\r\n'.join(response_params)


def handle_connection(conn, addr):
    # print(conn, addr)
    # time.sleep(60)  # 可以自行尝试打开注释,设置睡眠时间
    request = b""
    while EOL1 not in request and EOL2 not in request:
        request += conn.recv(1024)  # 注意在设置为非阻塞模式时这里会有报错,建议自己探索一下问题来源!!!!!!!

    print(request)
    current_thread = threading.currentThread()
    content_length = len(body.format(thread_name=current_thread.name).encode())
    print(current_thread.name)
    conn.send(response.format(thread_name=current_thread.name, length=content_length).encode())
    conn.close()


def main():
    # socket.AF_INET    用于服务器与服务器之间的网络通信
    # socket.SOCK_STREAM    基于TCP的流式socket通信
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 设置端口可复用,保证我们每次Ctrl C之后,快速再次重启
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serversocket.bind(('127.0.0.1', 8000))
    # 可参考:https://stackoverflow.com/questions/2444459/python-sock-listen
    serversocket.listen(10)
    print('http://127.0.0.1:8000')
    serversocket.setblocking(0)  # 设置socket为非阻塞模式

    try:
        i = 0
        while True:
            try:
                conn, address = serversocket.accept()
            except socket.error as e:
                if e.args[0] != errno.EAGAIN:
                    raise
                continue
            i += 1
            print(i)
            t = threading.Thread(target=handle_connection, args=(conn, address), name='thread-%s' % i)
            t.start()
    finally:
        serversocket.close()


if __name__ == '__main__':
    main()

运行代码,在浏览器中访问https://127.0.0.1:8000 ,多开几个浏览器窗口,看看有什么不同。再仔细阅读代码, 看看是如何处理的。
接着,还需要改一下代码。在handle_connection 下面有两行注释, 需要打开, 然后重启进程。再次访问页面,同时打开多个浏览器窗口(不是标签页),看看命令行上的输出。即便有一个任务耗时60 s ,也不影响下一个请求进来。看到了吧。
可以思考一下具体实现。这里主要涉及的知识点除了一开始用到过的socket 处理HTTP 协议外,还有下面这些。 

1、serversocket.setblocking(0): 目的是设置为非阻塞模式。所谓非阻塞,就是当前socket不会在accept 或者recv 时处于阻塞状态(必须等待有连接或者数据过来才执行下一步)。
2、serversocket.accept 外的try ... catch : 在非阻塞模式下, 当没有连接可以被接受时,就会抛出EAGAIN 错误。你可以简单理解为此时没有资源(连接) 可以使用,所以会抛出错误来,而这个错误是合理的。
3、多线程的使用:当我们通过accept 接受连接之后,就开启一个新的线程来处理这个连接, 而主程序可以继续在while Tru e 循环中处理其他连接。

当然, 这里的threadi咱也可以使用Python 的mul tipro ce ssi吨模块来替换,从而使用多进程的方式处理请求。另外,这里的非阻塞其实也不是必需的。你可以通过serversocket.setblocking(l )设置为阻塞模式,然后执行后看看结果是否一致。异步非阻塞是一种常见的模式。

另外,还有一种Web 模型,每次接受新请求时,就会产生一个子进程来处理,它跟多线程编程的模式类似。具体代码

import errno
import os
import socket

EOL1 = b'\n\n'
EOL2 = b'\n\r\n'
body = '<h1> 我是 HTTP Response HTTP Response HTTP Response<h1>'  # 响应体数据
response_params = [
    'HTTP/1.0 200 OK',
    'Date: Sat, 10 jun 2017 01:01:01 GMT',
    'Content-Type: text/plain; charset=utf-8',
    'Content-Length: {}\r\n'.format(len(body.encode())),
    body,
]
response = '\r\n'.join(response_params)


def handle_connection(conn, addr):
    pid = os.fork()  # 产生一个新的子进程
    if pid:  # 是否为父进程
        return

    # 子进程继续执行
    print(conn, addr)
    import time
    time.sleep(10)
    request = b""
    while EOL1 not in request and EOL2 not in request:
        request += conn.recv(1024)
    print('request handle by pid:', os.getpid())
    print(request)
    conn.send(response.encode())
    conn.close()


def main():
    serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    serversocket.bind(('127.0.0.1', 8000))
    serversocket.listen(5)
    serversocket.setblocking(0)
    print('http://127.0.0.1:8000')

    try:
        while True:
            try:
                conn, address = serversocket.accept()
            except socket.error as e:
                if e.args[0] != errno.EAGAIN:
                    raise
                continue
            handle_connection(conn, address)
    finally:
        serversocket.close()


if __name__ == '__main__':
    main()

到此为止,大概能理解每天访问的网站大概是怎么处理你的访问的了( 上面的示例代码只是基本原理)。下面可以看看Python 中所有框架在处理HTTP 请求时需要用到的东西。

WSGI一简单的WSGI Application(二)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值