web框架浅谈

一 web框架的本质

对于所有的Web应用,本质上就是一个socket服务端,用户的浏览器是一个socket客户端。通过socket实现其代码如下:

import socket

def handle_request(client):
    '''处理请求及返回数据'''
    buf = client.recv(1024)                  #接收请求数据
    client.send(b'HTTP/1.1 200 OK\r\n\r\n')
    client.send(b'Holle, Welcome to my first web.')

def main():
    sock = socket.socket()          #创建socket对象
    sock.bind(('127.0.0.1',8080))   #监听端口
    sock.listen(5)                  #设置最大监听数

    while True:
        conn,add = sock.accept()    #等待用户连接,默认accept阻塞,当有请求的时候继续往下执行
        handle_request(conn)        #把连接交给handle_request函数处理
        conn.close()                #关闭连接

if __name__ == '__main__':
    main()

再往深层次讲,当我们需要完成真正的web应用时,往往需要结合HTML文档以展示真正的页面功能。Web应用的本质就是:

  1. 客户端发送一个HTTP请求
  2. 服务端收到请求,生成一个HTML文档
  3. 服务端把HTML文档作为HTTP响应的body返回给客户端
  4. 客户端收到HTTP响应,从body中取出HTML文档并显示

所以,最简单的Web应用就是我们事先将HTML文档创建并保存,然后用一个HTTP服务器软件,接收用户请求,读取相应HTML文档,返回。如:常见Apache、Nginx、Lighttpd等静态服务器就完成这些功能。

如果要动态生成HTML,就需要把上述步骤自己来实现,但是,这样我们需要花大量的时间了解更底层的HTTP规范。正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。所以,需要一个统一的接口,让我们专心用Python编写Web业务。这个接口就是WSGI。

二 WSGI

WSGI,全称 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是为 Python 语言定义的 Web 服务器和 Web 应用程序或框架之间的一种简单而通用的接口(补充说明:对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等),实现服务器程序和应用程序间的解耦。它的定义很简单,只要求web开发者实现一个函数,就可以响应HTTP请求,结合之间socket的例子,如下:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h3>Holle, Welcome to my first web.<h3>']

上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

  • environ:一个包含所有HTTP请求的字典对象
  • start_response:一个发送HTTP响应的函数

application()函数的使用:

在application()函数中,调用:

start_response('200 OK', [('Content-Type', 'text/html')])

即发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数:

  • HTTP响应码:’200 OK’
  • 列表表示的HTTP Header,每个Header由一个包含两个字符串的元组组成

然后将函数的返回值b'<h3>b'Holle, Welcome to my first web.<h3>'作为HTTP响应的body发送给客户端浏览器。

有了WSGI就够了?NO,application()函数我们要怎么调用?怎样提供environ和start_response这两个参数?这里,我们就需要引入WSGI服务器来调用application()函数,常用的WSGI服务器如下:

server_names = {
    'cgi': CGIServer,
    'flup': FlupFCGIServer,
    'wsgiref': WSGIRefServer,
    'waitress': WaitressServer,
    'cherrypy': CherryPyServer,
    'paste': PasteServer,
    'fapws3': FapwsServer,
    'tornado': TornadoServer,
    'gae': AppEngineServer,
    'twisted': TwistedServer,
    'diesel': DieselServer,
    'meinheld': MeinheldServer,
    'gunicorn': GunicornServer,
    'eventlet': EventletServer,
    'gevent': GeventServer,
    'geventSocketIO':GeventSocketIOServer,
    'rocket': RocketServer,
    'bjoern' : BjoernServer,
    'auto': AutoServer,
}

基于WSGI实现简单web应用

Python标准库中提供了一个WSGI服务器——wsgiref,我们以此来完成web服务:

from wsgiref.simple_server import make_server

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [b'<h3>Holle, Welcome to my first web.<h3>']

if __name__ == '__main__':
    # 创建一个服务器,IP地址为空,端口是8080,处理函数application:
    httpd = make_server('', 8080, application)
    print("Serving HTTP on port 8080...")
    # 监听HTTP请求
    httpd.serve_forever()

无论多么复杂的Web应用程序,入口都是一个WSGI处理函数。HTTP请求的所有输入信息都可以通过environ获得,HTTP响应的输出都可以通过start_response()加上函数返回值作为Body。

复杂的Web应用程序,光靠一个WSGI函数来处理还是太底层了,我们需要在WSGI之上再抽象出Web框架,进一步简化Web开发。

三 自定义web框架

3.1 简易框架

通过python标准库提供的wsgiref模块开发一个自己的Web框架,前面的例子只能访问一个url,下面我们根据不同url,返回不同页面。

from wsgiref.simple_server import make_server

def func1():
    return [b'url1 page']       #如有中文字符需要解码,如return ['url1 页面'.encode('gbk')]

def func2():
    return [b'url2 page']

#定义一个列表将url和上面的函数做一个对应
url_list = [
    ('/url1/', func1),
    ('/url2/', func2),
]

def application(environ, start_response):     #environ包含请求相关信息
    start_response('200 OK', [('Content-Type', 'text/html')])
    url = environ['PATH_INFO']                #获取需要访问的url
    func = None
    #循环列表,查找列表中是否存在访问的url,存在则执行函数并返回,不存在则返回404
    for item in url_list:
        if item[0] == url:
            func = item[1]
            break
    if func:
        return func()
    else:
        return [b'404 not found',]

if __name__ == '__main__':
    httpd = make_server('', 8080, application)
    print("Serving HTTP on port 8080...")
    httpd.serve_forever()

3.2 静态页面(模板引擎)

在上一步骤中,对于func1,func2均返回给用户浏览器一个简单的字符串,在现实的Web请求中一般会返回一个复杂的符合HTML规则的字符串,所以我们一般将要返回给用户的HTML写在指定文件中,然后再返回。如:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>url1 页面</h3>
</body>
</html>
url1.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>url2 页面</h3>
</body>
</html>
url2.html
from wsgiref.simple_server import make_server

def func1():
    f = open('url1.html', 'rb')
    data = f.read()
    f.close()
    return [data]

def func2():
    f = open('url2.html', 'rb')
    data = f.read()
    f.close()
    return [data]

url_list = [
    ('/url1/', func1),
    ('/url2/', func2),
]

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    url = environ['PATH_INFO']
    func = None
    for item in url_list:
        if item[0] == url:
            func = item[1]
            break
    if func:
        return func()
    else:
        return [b'404 not found',]

if __name__ == '__main__':
    httpd = make_server('', 8080, application)
    print("Serving HTTP on port 8080...")
    httpd.serve_forever()

3.3 动态页面

上一步我们已经实现了静态页面的返回,那如何返回动态页面呢?常用以下两种方式:

  • 自定义一套特殊的语法,进行替换
  • 使用开源工具jinja2,遵循其指定语法

安装jinja2

pip install jinja2
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>url1 页面</h3>
    <h4>{{ name }}</h4>
    <ul>
        {% for item in test_list %}
            <li>{{ item }}</li>
        {% endfor %}
    </ul>
</body>
</html>
url1.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h3>url2 页面</h3>
    <h4>当前时间为:@time@</h4>
</body>
</html>
url2.html
from wsgiref.simple_server import make_server
from jinja2 import Template

def func1():
    '''利用jinja2实现动态'''
    f = open('url1.html', 'r', encoding='utf8')
    data = f.read()
    f.close()
    template = Template(data)
    result = template.render(name='joe1991',test_list=['test1','test2','test3'])  #当然我们可以从数据库中获取数据,以实现真正意义上的动态
    return [result.encode('utf8')]

def func2():
    '''利用字符串替换实现动态'''
    f = open('url2.html', 'r', encoding='utf8')
    data = f.read()
    f.close()
    import time
    ctime = time.strftime("%Y-%m-%d %X", time.localtime())
    print(ctime,type(ctime))
    result = data.replace('@time@',ctime)    #replace有返回值,必须要接收
    return [result.encode('utf8')]

url_list = [
    ('/url1/', func1),
    ('/url2/', func2),
]

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    url = environ['PATH_INFO']
    func = None
    for item in url_list:
        if item[0] == url:
            func = item[1]
            break
    if func:
        return func()
    else:
        return [b'404 not found',]

if __name__ == '__main__':
    httpd = make_server('', 8080, application)
    print("Serving HTTP on port 8080...")
    httpd.serve_forever()

访问url1结果:

image

访问url2结果:

image

当然,以上自定义web框架我们也可以通过socket做。

猜你喜欢

转载自www.cnblogs.com/Joe1991/p/11732701.html