Fork me on GitHub

WSGI协议

一、socket服务器

(一)Web服务器

socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。

socket是应用层和TCP/IP协议中间通信的软件层,它是一组接口,在设计模式中,socket其实就是一个门面模式,它把复杂的TCP/IP协议封装隐藏在socket接口后,让socket去组织数据,以符合指定协议,所以只需遵循socket规定去编程就可以。

如:

  • server.py
import socket

sk = socket.socket()

sk.bind(("127.0.0.1", 9600))

sk.listen()

while True:
    conn, addr = sk.accept()
    data = conn.recv(1024)
    conn.send(b"world")
    conn.close()
  • client.py
import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(("127.0.0.1", 9600))

sock.send(b"hello")
sock.close()

从上面可以看到是基于TCP/IP协议来完成的,而Web服务器本质就是socket服务器,然后浏览器来进行访问,但是浏览器发送什么数据,如何发送,况且浏览器有很多,需要有一个统一的协议来规定,这个就是HTTP协议。

规定了首先返回状态行:

import socket

sk = socket.socket()

sk.bind(("127.0.0.1", 9600))

sk.listen()

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收数据
    data = conn.recv(1024)
    # 返回状态行
    conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
    # 返回体
    conn.send(b"world")
    # 关闭连接
    conn.close()

当浏览器访问 http://127.0.0.1:9600/ 后页面就会收到服务器的响应体:world。

(二)模板文件

上述的socket web服务器返回的内容可以写在一个文件中,然后读取文件的内容进行访问:

  • index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h1>hello world!</h1>
</body>
</html>
  • server.py
import socket

sk = socket.socket()

sk.bind(("127.0.0.1", 9600))

sk.listen()

while True:
    # 等待连接
    conn, addr = sk.accept()
    # 接收数据
    data = conn.recv(1024)
    # 返回状态行
    conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
    # 返回体,读取文件内容
    with open("index.html", mode="rb") as f:
        res = f.read()
    conn.send(res)
    # 关闭连接
    conn.close()

(三)路由系统

import socket

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('127.0.0.1', 9600))
sock.listen()


def index():
    return b"index"


def home():
    return b"home"


urlpattern = [
    ("/index", index),
    ("/home", home)
]

while True:
    conn, addr = sock.accept()
    data = conn.recv(1024)
    data = str(data, encoding="utf8")
    url = data.split("\r\n")[0].split()[1]
    print(url)
    func = None
    for path_tuple in urlpattern:
        if path_tuple[0] == url:
            func = path_tuple[1]
            break
    if func:
        response = func()
    else:
        response = b"404 not found!"
    # 返回响应状态行
    conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
    # 返回响应体
    conn.send(response)
    conn.close()

二、服务器程序和应用程序

(一)WSGI协议

web框架本质就是一个socket服务端。

web框架功能:

  • socket收发消息
  • 根据不同的路径返回不同的内容
  • 可以返回页面

对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。

服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。

应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。

这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。

这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

WSGI(Web Server Gateway Interface)就是一种规范,它定义了使用Python编写的web应用程序与web服务器程序之间的接口格式,实现web应用程序与web服务器程序间的解耦。

那么WSGI定义的究竟是一种什么样的规范呢?

  • 应用程序/框架
  • 服务器/网关

1、应用程序/框架

应用程序对象是一个接受两个参数的可调用对象。函数、方法、类或带有__call__方法的实例都可以用作应用程序对象。应用程序对象必须能够被多次调用,因为几乎所有服务器/网关(CGI 除外)都会发出此类重复请求。

这是两个示例应用程序对象;一个是函数,另一个是类:

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!\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!\n"

2、服务器/网关

服务器或网关针对它从 HTTP 客户端接收到的每个针对应用程序的请求调用一次可调用应用程序。为了说明这一点,这里有一个简单的 CGI 网关,实现为采用应用程序对象的函数。如果服务器中触发可调用应用程序,就是可符合WSGI协议的WSGI服务器。

import os, sys

def run_with_cgi(application):

    environ = dict(os.environ.items())
    environ['wsgi.input']        = sys.stdin
    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):
        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
             sys.stdout.write('Status: %s\r\n' % status)
             for header in response_headers:
                 sys.stdout.write('%s: %s\r\n' % header)
             sys.stdout.write('\r\n')

        sys.stdout.write(data)
        sys.stdout.flush()

    def start_response(status, response_headers, exc_info=None):
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[0], exc_info[1], exc_info[2]
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]
        return write

    result = application(environ, start_response) # 触发应用程序
    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()

(二)WSGI服务器

WSGI协议定义了应用程序和服务器之间的规范,符合这个规范即可。

常用的WSGI服务器有uwsgi、Gunicorn。而Python标准库提供的独立WSGI服务器叫wsgiref,Django开发环境用的就是这个模块来做服务器。

 符合WSGI协议的服务器会回调start_response方法:

 result = application(environ, start_response) # 触发应用程序

 总的来说WSGI协议主要有三点:

  • 接受environ 和start_response两个参数
  • 内部调用 start_respons生成header
  • 返回一个可迭代的响应体

那么可以使用Python内置wsgiref模块来实现web服务器:

from wsgiref.simple_server import make_server


def index():
    return b"index"


def home():
    return b"home"


urlpattern = [
    ("/index", index),
    ("/home", home)
]


def simple_app(environ, start_response):
    status = "200 OK"
    response_headers = [('Content-Type', 'text/plain')]
    start_response(status, response_headers)
    current_path = environ.get("PATH_INFO")
    func = None
    for path_tuple in urlpattern:
        if path_tuple[0] == current_path:
            func = path_tuple[1]
            break
    if func:
        response = func()
    else:
        response = b"404 not found!"
    return [response, ]


if __name__ == '__main__':
    srv = make_server("127.0.0.1", 9000, simple_app)
    srv.serve_forever()

 

 

 

posted @ 2022-12-02 20:12  iveBoy  阅读(262)  评论(0编辑  收藏  举报
TOP