web 框架简介1 socket wsgi自定义框架 jinja2模板语言

Web框架本质

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

 

#!/usr/bin/env python
#coding:utf-8
  
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()

  

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

WSGI(Web Server Gateway Interface)是一种规范,它定义了使用python编写的web app与web server之间接口格式,实现web app与web server间的解耦。

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

记得在python2版本上运行哦 3的话会报错

 

#!/usr/bin/env python
#coding:utf-8
 
from wsgiref.simple_server import make_server
 
def RunServer(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'
 
if __name__ == '__main__':
    httpd = make_server('', 8000, RunServer)
    print "Serving HTTP on port 8000..."
    httpd.serve_forever() 

 

 

运行WSGI服务

我们先编写hello.py,实现Web应用程序的WSGI处理函数:

# hello.py

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'
然后,再编写一个server.py,负责启动WSGI服务器,加载application()函数:

# server.py
# 从wsgiref模块导入:
from wsgiref.simple_server import make_server
# 导入我们自己编写的application函数:
from hello import application

# 创建一个服务器,IP地址为空,端口是8000,处理函数是application:
httpd = make_server('', 8000, application)
print "Serving HTTP on port 8000..."
# 开始监听HTTP请求:
httpd.serve_forever()
确保以上两个文件在同一个目录下,然后在命令行输入python server.py来启动WSGI服务器:
运行wsgi服务
#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'
from wsgiref.simple_server import make_server

def RunServer(environ,start_response):
    start_response("200 OK",[("Content-Type","text/html")])
    return "<h1> hello world </h1>"

if __name__ == '__main__':

    http = make_server("",8886,RunServer)  #host port demo_app
    http.serve_forever()


"""
def demo_app(environ,start_response):
    from io import StringIO
    stdout = StringIO()
    print("Hello world!", file=stdout)
    print(file=stdout)
    h = sorted(environ.items())
    for k,v in h:
        print(k,'=',repr(v), file=stdout)
    start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
    return [stdout.getvalue().encode("utf-8")]

WSGI接口

阅读: 44638
了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是:

浏览器发送一个HTTP请求;

服务器收到请求,生成一个HTML文档;

服务器把HTML文档作为HTTP响应的Body发送给浏览器;

浏览器收到HTTP响应,从HTTP Body取出HTML文档并显示。

所以,最简单的Web应用就是先把HTML用文件保存好,用一个现成的HTTP服务器软件,接收用户请求,从文件中读取HTML,返回。Apache、Nginx、Lighttpd等这些常见的静态服务器就是干这件事情的。

如果要动态生成HTML,就需要把上述步骤自己来实现。不过,接受HTTP请求、解析HTTP请求、发送HTTP响应都是苦力活,如果我们自己来写这些底层代码,还没开始写动态HTML呢,就得花个把月去读HTTP规范。

正确的做法是底层代码由专门的服务器软件实现,我们用Python专注于生成HTML文档。因为我们不希望接触到TCP连接、HTTP原始请求和响应格式,所以,需要一个统一的接口,让我们专心用Python编写Web业务。

这个接口就是WSGI:Web Server Gateway Interface。

WSGI接口定义非常简单,它只要求Web开发者实现一个函数,就可以响应HTTP请求。我们来看一个最简单的Web版本的“Hello, web!”:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    return '<h1>Hello, web!</h1>'
上面的application()函数就是符合WSGI标准的一个HTTP处理函数,它接收两个参数:

environ:一个包含所有HTTP请求信息的dict对象;

start_response:一个发送HTTP响应的函数。

在application()函数中,调用:

start_response('200 OK', [('Content-Type', 'text/html')])
就发送了HTTP响应的Header,注意Header只能发送一次,也就是只能调用一次start_response()函数。start_response()函数接收两个参数,一个是HTTP响应码,一个是一组list表示的HTTP Header,每个Header用一个包含两个str的tuple表示。

通常情况下,都应该把Content-Type头发送给浏览器。其他很多常用的HTTP Header也应该发送。

然后,函数的返回值'<h1>Hello, web!</h1>'将作为HTTP响应的Body发送给浏览器。

有了WSGI,我们关心的就是如何从environ这个dict对象拿到HTTP请求信息,然后构造HTML,通过start_response()发送Header,最后返回Body。

整个application()函数本身没有涉及到任何解析HTTP的部分,也就是说,底层代码不需要我们自己编写,我们只负责在更高层次上考虑如何响应请求就可以了。

不过,等等,这个application()函数怎么调用?如果我们自己调用,两个参数environ和start_response我们没法提供,返回的str也没法发给浏览器。

所以application()函数必须由WSGI服务器来调用。有很多符合WSGI规范的服务器,我们可以挑选一个来用。但是现在,我们只想尽快测试一下我们编写的application()函数真的可以把HTML输出到浏览器,所以,要赶紧找一个最简单的WSGI服务器,把我们的Web应用程序跑起来。

"""

复杂一点的

#!/usr/bin/env python
# _*_ coding:utf-8 _*_
__author__ = 'liujianzuo'
from wsgiref.simple_server import make_server

def new():
    return "new"

def index():
    return "index"

URLS = {
    "/new":new,
    "/index":index,
}


def RunServer(environ,start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    url = environ["PATH_INFO"]
    if url in URLS.keys():
        func_names = URLS[url]
        ret = func_names()
    else:
        return "404"

    return ret

    # if url == "/new":
    #   ret =  new()
    # elif url == "/index":
    #     ret = index()
    # else:
    #     return "404"
    # return ret


if __name__ == '__main__':

    http = make_server('',8888,RunServer)  #host port demo_app
    http.serve_forever()

 

拆分一下上面的代码

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        @media (min-width: 400px) {
            .w6{
                width: 50%;
                float: left;
                background-color: #2459a2;
            }
        }
        @media (min-width: 600px) {
            .w6{
                width: 50%;
                float: left;
                background-color: red;
            }
        }

    </style>
</head>
<body>
    <div class="w6">
        123
    </div>
    <div class="w6">456</div>
</body>
</html>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        @media (min-width: 400px) {
            .w6{
                width: 50%;
                float: left;
                background-color: #2459a2;
            }
        }
        @media (min-width: 600px) {
            .w6{
                width: 50%;
                float: left;
                background-color: red;
            }
        }

    </style>
</head>
<body>
    <h1>{{ name }}</h1>
    <ul>
        {% for item in user_list %}
            <li>{{item}}</li>
        {% endfor %}
    </ul>
    <div class="w6">
        123
    </div>
    <div class="w6">456</div>
</body>
</html>
s1.html

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-
import os
import time
from jinja2 import Template
def new():
    # f = open(os.path.join('views', 's1.html' ), 'r')
    # data = f.read()
    # f.close()
    # new_data = data.replace("{{item}}", str(time.time()))
    # return new_data
    f = open(os.path.join('views','s1.html'))
    result = f.read()

    template = Template(result)
    data = template.render(name='John Doe', user_list=['alex', 'eric','xiaojun', 'sb'])
    return data.encode('utf-8')

def index():
    f = open(os.path.join('views', 'index.html'), 'r')
    data = f.read()
    f.close()
    return data

def home():
    return 'home'
controller.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from wsgiref.simple_server import make_server
from urls import URLS

def RunServer(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/html')])
    # 获取用户URL
    url = environ['PATH_INFO']
    if url in URLS.keys():
        func_name = URLS[url]
        ret = func_name()
    else:
        ret = "404"
    return ret

if __name__ == "__main__":
    httpd = make_server('', 8000, RunServer)
    httpd.serve_forever()
start.py
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import controller

URLS = {
    "/new": controller.new,
    "/index": controller.index,
    "/home": controller.home,
}
urls

 

自定义Web框架

一、框架

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

 

#!/usr/bin/env python
#coding:utf-8
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 = 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, RunServer)
    print "Serving HTTP on port 8000..."
    httpd.serve_forever()

  

2、模板引擎

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

<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <h1>Index</h1>

</body>
</html>
index.html
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <form>
        <input type="text" />
        <input type="text" />
        <input type="submit" />
    </form>
</body>
</html>
login.html
#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
from wsgiref.simple_server import make_server
 
 
def index():
    # return 'index'
    f = open('index.html')
    data = f.read()
    return data
 
 
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()

  

对于上述代码,虽然可以返回给用户HTML的内容以现实复杂的页面,但是还是存在问题:如何给用户返回动态内容?

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

 

 

index.html

<!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>

 

#!/usr/bin/env python
# -*- coding:utf-8 -*-
 
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()

  遵循jinja2的语法规则,其内部会对指定的语法进行相应的替换,从而达到动态的返回内容,对于模板引擎的本质,参考wusir一篇博客:白话tornado源码之褪去模板外衣的前戏

 

  

 

posted @ 2016-07-21 18:24  众里寻,阑珊处  阅读(287)  评论(0编辑  收藏  举报
返回顶部