Web框架的本质

Web框架的本质

web应用

  众所周知,对于所有的web应用,本质上其实就是一个socket服务端,用户的浏览器其实就是一个socket客户端

import socket
  
def handle_request(client):
    buf = client.recv(1024)
    client.send("HTTP/1.1 200 OK\r\n\r\n")
    client.send("Hello, Seven")
  
def main():
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost',8000))
    sock.listen(5)
  
    while True:
        connection, address = sock.accept()
        handle_request(connection)
        connection.close()
  
if __name__ == '__main__':
    main()
View Code

  上述通过socket来实现了其本质,而对于真实开发中的python web程序来说,一般会分为两部分:服务器程序和应用程序。服务器程序负责对socket服务器进行封装,并在请求到来时,对请求的各种数据进行整理。应用程序则负责具体的逻辑处理。为了方便应用程序的开发,就出现了众多的Web框架,例如:Django、Flask、web.py 等。不同的框架有不同的开发方式,但是无论如何,开发出的应用程序都要和服务器程序配合,才能为用户提供服务。这样,服务器程序就需要为不同的框架提供不同的支持。这样混乱的局面无论对于服务器还是框架,都是不好的。对服务器来说,需要支持各种不同框架,对框架来说,只有支持它的服务器才能被开发出的应用使用。这时候,标准化就变得尤为重要。我们可以设立一个标准,只要服务器程序支持这个标准,框架也支持这个标准,那么他们就可以配合使用。一旦标准确定,双方各自实现。这样,服务器可以支持更多支持标准的框架,框架也可以使用更多支持标准的服务器。

WSGI

  Web服务网关接口(Web Server Gateway Interface,简称“WSGI”)是一种在web服务器和Python web应用程序或框架之间的标准接口。具体的来说,WSGI是一种规范,它定义了使用python编写的web app与web server之间接口格式(Web服务器如何与Python应用程序进行交互),使得使用Python写的Web应用程序可以和Web服务器对接起来,实现web app与web server间的解耦。

  通过标准化web服务器和Python web应用程序或框架之间的行为和通信,WSGI使得编写可移植的的Python web代码变为可能,使其能够部署在任何 WSGI可用的 web 服务器 上。WSGI的文档在 PEP 3333

关于wsgi的更多信息:https://segmentfault.com/a/1190000003069785

  python标准库提供的独立WSGI服务器称为wsgiref。

from wsgiref.simple_server import make_server

def RunServer(environ, start_response):
    """
    :param environ: 包含用户请求的所有信息,是wsgi封装好的
    :param start_response: 一个发送HTTP响应的函数
    """
    start_response('200 OK', [('Content-Type', 'text/html')])
    return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]

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

"""
详解environ, start_response参数

environ参数
environ参数是一个Python的字典,里面存放了所有和客户端相关的信息,这样application对象就能知道客户端请求的资源是什么,请求中带了什么数据等。environ字典包含了一些CGI规范要求的数据,以及WSGI规范新增的数据,还可能包含一些操作系统的环境变量以及Web服务器相关的环境变量。我们来看一些environ中常用的成员:

首先是CGI规范中要求的变量:

REQUEST_METHOD: 请求方法,是个字符串,'GET', 'POST'等
SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据path的一部分来决定请求由哪个virtual host处理
PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分
QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容
CONTENT_TYPE: HTTP headers中的content-type内容
CONTENT_LENGTH: HTTP headers中的content-length内容
SERVER_NAME和SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径
SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
HTTP_: 和HTTP请求中的headers对应。
WSGI规范中还要求environ包含下列成员:

wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0
wsgi.url_scheme:http或者https
wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body
wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里
wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True
wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True
wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True
上面列出的这些内容已经包括了客户端请求的所有数据,足够application对象处理客户端请求了。

start_resposne参数
start_response是一个可调用对象,接收两个必选参数和一个可选参数:

status: 一个字符串,表示HTTP响应状态字符串
response_headers: 一个列表,包含有如下形式的元组:(header_name, header_value),用来表示HTTP响应的headers
exc_info(可选): 用于出错时,server需要返回给浏览器的信息
当application对象根据environ参数的内容执行完业务逻辑后,就需要返回结果给server端。我们知道HTTP的响应需要包含status,headers和body,所以在application对象将body作为返回值return之前,需要先调用start_response(),将status和headers的内容返回给server,这同时也是告诉server,application对象要开始返回body了。
"""
View Code

Web 框架

  Web 框架就是一份代码库,它能帮助开发人员创建可靠、可扩展、易维护的 Web 应用。

广义地说,web框架包含一系列库和一个主要的handler,这样你就能够构建自己的代码来实现web应用(比如说一个交互式的网站)。大多数web框架包含模式和工具,至少实现以下功能:

URL路由(URL Routing)
将输入的HTTP请求匹配到特定的Python代码用来调用
请求和响应对象(Request and Response Objects)
封装来自或发送给用户浏览器的信息
模板引擎(Template Engine)
能够将实现应用的Python代码逻辑和其要产生输出的HTML(或其他)分离开
web服务器开发(Development Web Server)
在开发机上运行HTTP服务器,从而快速开发;当文件更新时自动更新服务端代码。

通过python标准库提供的wsgiref模块开发一个自己的Web框架

from wsgiref.simple_server import make_server

def index():
    return 'index'

def login():
    return 'login'

def routers():
    # 路由函数
    urlpatterns = (
        ('/index/', index),
        ('/login/', login),
    )

    return urlpatterns

def RunServer(environ, start_response):
    # 响应头
    start_response('200 OK', [('Content-Type', 'text/html')])
    # 获取客户端url信息
    url = environ['PATH_INFO']
    # 执行路由函数,获取路由元组
    urlpatterns = routers()
    func = None
    for item in urlpatterns:
        # 如果用户请求的url和我们定义的url匹配,将路由的url所对应的函数赋给func
        if item[0] == url:
            func = item[1]
            break
    if func:
        return func()
    # 执行里面的方法
    else:
        return '404 not found'


if __name__ == '__main__':
    httpd = make_server('', 8000, RunServer)
    print(    "Serving HTTP on port 8000...")
    httpd.serve_forever()
View Code

模板

多数WSGI应用响应HTTP请求,从而服务于HTML或其他标记语言中的内容。关注点分离的概念建议我们使用模板,而不是直接由Python生成文本内容。模板引擎管理一系列的模板文件,其系统的层次性和包容性避免了不必要的重复。模板引擎负责渲染(产生)实际内容,用由应用生成的动态内容填充静态内容。

由于模板文件有时是由设计师或者前端开发者编写,处理不断增长的复杂度会变得困难。

一些通用的良好实践应用到了部分应用中,情景包括传递动态内容到模板引擎和模板自身中。

  • 模板文件只应传递需要渲染的动态内容。避免传递附加的“以防万一”的内容: 需要时添加遗漏的变量比移除可能不用的变量要来的容易。
  • 许多模板引擎允许在模板中编写复杂语句或者赋值,也有许多允许一些Python代码 在模板中等价编写。这种便利会导致复杂度不可控地增加,也使得查找bug变得更加 困难。
  • 我们常常需要混合JavaScript模板和HTML模板。一种聪明的做法是孤立出HTML 模板传递部分变量内容到JavaScript代码中的部分。

Jinja2

Jinja2 是一个和Django模板系统类似的模板引擎,并有一些额外的特性。它是一种基于文本的模板语言,因此能够用于生成任何标记内容。它允许定制过滤(filter)、标签、测试和全局内容,不像在Django框架中实现的模板系统,它还允许调用函数

遵循jinja2的语法规则,其内部会对指定的语法进行相应的替换,从而达到动态的返回内容

 在Jinja2中重要的html标签:

{# 这是注释 #}

{# 下一个标签是输出变量: #}
{{title}}

{# 区块标签,能通过继承其他html代码来替换区块内容 #}
{% block head %}
<h1>This is the head!</h1>
{% endblock %}

{# 数组迭代输出 #}
{% for item in list %}
<li>{{ item }}</li>
{% endfor %}

注意“{% ... %}"与"{{ .. }}", 前者用来执行一个循环或者一个赋值语句,后者用来打印一个变量。 

from wsgiref.simple_server import make_server
from jinja2 import Template

def index():
    # return 'index'

    # template = Template('Hello {{ name }}!')
    # result = template.render(name='John Doe')

    f = open('index.html')
    result = f.read()
    template = Template(result)
    data = template.render(name='John Doe', user_list=['alex', 'eric'])
    return data.encode('utf-8')

def login():
    # return 'login'
    f = open('login.html')
    data = f.read()
    return data

def routers():
    urlpatterns = (
        ('/index/', index),
        ('/login/', login),
    )

    return urlpatterns


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


if __name__ == '__main__':
    httpd = make_server('', 8000, run_server)
    print("Serving HTTP on port 8000...")
    httpd.serve_forever()
demo
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>{{name}}</h1>

    <ul>
        {% for item in user_list %}
        <li>{{item}}</li>
        {% endfor %}
    </ul>

</body>
</html>

index.html
index.html

 更多:http://docs.jinkan.org/docs/jinja2/

 

 参考:http://www.cnblogs.com/wupeiqi/articles/5237672.html

 参考:http://pythonguidecn.readthedocs.io/zh/latest/scenarios/web.html#

posted @ 2017-04-09 16:06  似是故人来~  阅读(380)  评论(0编辑  收藏  举报