zoukankan      html  css  js  c++  java
  • Python学习--20 Web开发

    HTTP格式

    HTTP协议是基于TCP和IP协议的。HTTP协议是一种文本协议。

    每个HTTP请求和响应都遵循相同的格式,一个HTTP包含Header和Body两部分,其中Body是可选的。

    HTTP请求格式:

    GET:

    GET /path HTTP/1.1
    Header1: Value1
    Header2: Value2
    Header3: Value3
    

    POST:

    POST /path HTTP/1.1
    Header1: Value1
    Header2: Value2
    Header3: Value3
    
    body data goes here...
    

    Header部分每行用 换行,每行里键名和键值之间以: 分割,注意冒号后有个空格。

    当遇到 时,Header部分结束,后面的数据全部是Body。

    HTTP响应格式:

    200 OK
    Header1: Value1
    Header2: Value2
    Header3: Value3
    
    body data goes here...
    

    HTTP响应如果包含body,也是通过 来分隔的。

    请再次注意,Body的数据类型由Content-Type头来确定,如果是网页,Body就是文本,如果是图片,Body就是图片的二进制数据。

    Body数据是可以被压缩的,如果看到Content-Encoding,说明网站使用了压缩。最常见的压缩方式是gzip。

    WSGI接口

    了解了HTTP协议的格式后,我们可以理解一个Web应用的本质:
    1、浏览器发送HTTP请求给服务器;
    2、服务器接收请求后,生成HTML;
    3、服务器把生成的HTML作为HTTP响应的body返回给浏览器;
    4、浏览器接收到HTTP响应后,解析HTTP里body并显示。

    接受HTTP请求、解析HTTP请求、发送HTTP响应实现起来比较复杂,有专门的服务器软件来实现,例如Nginx,Apache。我们要做的就是专注于生成HTML文档。

    Python里也提供了一个比较底层的WSGI(Web Server Gateway Interface)接口来实现TCP连接、HTTP原始请求和响应格式。实现了该接口定义的内容,就可以实现类似Nginx、Apache等服务器的功能。

    WSGI接口定义要求Web开发者实现一个函数,就可以响应HTTP请求,示例:

    def application(environ, start_response):
        start_response('200 OK', [('Content-Type', 'text/html')])
        return [b'<h1>Hello, web!</h1>']
    

    这是一个简单的文本版本的Hello, web!

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

    environ:一个包含所有HTTP请求信息的dict对象;
    start_response:一个发送HTTP响应的函数。
    

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

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

    但是,application()函数由谁来调用呢?因为这里的参数environstart_response我们没法提供,返回的bytes也没法发给浏览器。

    application()函数必须由WSGI服务器来调用。

    有很多符合WSGI规范的服务器,Python提供了一个最简单的WSGI服务器,可以把我们的Web应用程序跑起来。这个模块叫wsgiref,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。

    运行WSGI服务

    有了wsgiref,我们可以非常快的实现一个简单的web服务器:

    # coding: utf-8
    
    from wsgiref.simple_server import make_server
    
    def application(environ, start_response):
        print(environ)
        start_response('200 OK', [('Content-Type', 'text/html')])
        return [b'<h1>Hello web!</h1>']
    
    print('HTTP server is running on http://127.0.0.1:9999')
    
    # 创建一个服务器,IP地址可以为空,端口是9999,处理函数是application:
    httpd = make_server('', 9999, application)
    httpd.serve_forever()
    

    运行后访问http://127.0.0.1:9999/,会看到:

    Hello web!
    

    扩展知识:
    make_server()里第一个参数如果为空,实际等效于0.0.0.0 ,表示监听本地所有ip地址(包括127.0.0.1)。

    通过Chrome浏览器的控制台,我们可以查看到浏览器请求和服务器响应信息:

    # 请求信息:
    GET / HTTP/1.1
    Host: 127.0.0.1:9999
    Connection: keep-alive
    Cache-Control: max-age=0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36
    Accept-Encoding: gzip, deflate, sdch
    Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
    Cookie: _ga=GA1.1.948200530.1463673425
    
    # 响应信息:
    HTTP/1.0 200 OK
    Date: Sun, 12 Feb 2017 05:20:31 GMT
    Server: WSGIServer/0.2 CPython/3.4.3
    Content-Type: text/html
    Content-Length: 19
    
    <h1>Hello web!</h1>
    

    我们再看终端的输出信息:

    $ python user_wsgiref_server.py
    HTTP server is running on http://127.0.0.1:9999
    127.0.0.1 - - [12/Feb/2017 13:18:38] "GET / HTTP/1.1" 200 19
    127.0.0.1 - - [12/Feb/2017 13:18:39] "GET /favicon.ico HTTP/1.1" 200 19
    

    如果我们打印environ参数信息,会看到如下值:

    {
        "SERVER_SOFTWARE": "WSGIServer/0.1 Python/2.7.5",
        "SCRIPT_NAME": "",
        "REQUEST_METHOD": "GET",
        "SERVER_PROTOCOL": "HTTP/1.1",
        "HOME": "/root",
        "LANG": "en_US.UTF-8",
        "SHELL": "/bin/bash",
        "SERVER_PORT": "9999",
        "HTTP_HOST": "dev.banyar.cn:9999",
        "HTTP_UPGRADE_INSECURE_REQUESTS": "1",
        "XDG_SESSION_ID": "64266",
        "HTTP_ACCEPT": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
        "wsgi.version": "0",
        "wsgi.errors": "",
        "HOSTNAME": "localhost",
        "HTTP_ACCEPT_LANGUAGE": "zh-CN,zh;q=0.8,en;q=0.6",
        "PATH_INFO": "/",
        "USER": "root",
        "QUERY_STRING": "",
        "PATH": "/usr/local/php/bin:/usr/local/php/sbin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin",
        "HTTP_USER_AGENT": "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",
        "HTTP_CONNECTION": "keep-alive",
        "SERVER_NAME": "localhost",
        "REMOTE_ADDR": "192.168.0.101",
        "wsgi.url_scheme": "http",
        "CONTENT_LENGTH": "",
        "GATEWAY_INTERFACE": "CGI/1.1",
        "CONTENT_TYPE": "text/plain",
        "REMOTE_HOST": "",
        "HTTP_ACCEPT_ENCODING": "gzip, deflate, sdch"
    }
    

    为显示方便,已精简部分信息。有了环境变量信息,我们可以对程序做些修改,可以动态显示内容:

    def application(environ, start_response):
        print(environ['PATH_INFO'])
        start_response('200 OK', [('Content-Type', 'text/html')])
        body = '<h1>Hello %s!</h1>'  % (environ['PATH_INFO'][1:] or 'web' )
        return [body.encode('utf-8')]
    

    以上使用了environ里的PATH_INFO的值。我们在浏览器输入http://127.0.0.1:9999/python,浏览器会显示:

    Hello python!
    

    终端的输出信息:

    $ python user_wsgiref_server.py
    HTTP server is running on http://127.0.0.1:9999
    /python
    127.0.0.1 - - [12/Feb/2017 13:54:57] "GET /python HTTP/1.1" 200 22
    /favicon.ico
    127.0.0.1 - - [12/Feb/2017 13:54:58] "GET /favicon.ico HTTP/1.1" 200 27
    

    web框架

    实际项目开发中,我们不可能使用swgiref来实现服务器,因为WSGI提供的接口虽然比HTTP接口高级了不少,但和Web App的处理逻辑比,还是比较低级。我们需要使用成熟的web框架。

    由于用Python开发一个Web框架十分容易,所以Python有上百个开源的Web框架。部分流行框架:

    Flask:轻量级Web应用框架;
    Django:全能型Web框架;
    web.py:一个小巧的Web框架;
    Bottle:和Flask类似的Web框架;
    Tornado:Facebook的开源异步Web框架
    

    Flask

    Flask是一个使用 Python 编写的轻量级 Web 应用框架。其 WSGI 工具箱采用 Werkzeug ,模板引擎则使用 Jinja2 。

    安装非常简单:

    pip install flask
    

    控制台输出:

    Collecting flask
      Downloading Flask-0.12-py2.py3-none-any.whl (82kB)
        100% |████████████████████████████████| 92kB 163kB/s
    Collecting itsdangerous>=0.21 (from flask)
      Downloading itsdangerous-0.24.tar.gz (46kB)
        100% |████████████████████████████████| 51kB 365kB/s
    Collecting click>=2.0 (from flask)
      Downloading click-6.7-py2.py3-none-any.whl (71kB)
        100% |████████████████████████████████| 71kB 349kB/s
    Collecting Jinja2>=2.4 (from flask)
      Downloading Jinja2-2.9.5-py2.py3-none-any.whl (340kB)
        100% |████████████████████████████████| 348kB 342kB/s
    Collecting Werkzeug>=0.7 (from flask)
      Downloading Werkzeug-0.11.15-py2.py3-none-any.whl (307kB)
        100% |████████████████████████████████| 317kB 194kB/s
    Collecting MarkupSafe>=0.23 (from Jinja2>=2.4->flask)
      Downloading MarkupSafe-0.23.tar.gz
    Building wheels for collected packages: itsdangerous, MarkupSafe
      Running setup.py bdist_wheel for itsdangerous ... done
    Successfully built itsdangerous MarkupSafe
    Installing collected packages: itsdangerous, click, MarkupSafe, Jinja2, Werkzeug, flask
    Successfully installed Jinja2-2.9.5 MarkupSafe-0.23 Werkzeug-0.11.15 click-6.7 flask-0.12 itsdangerous-0.24
    

    安装完flask会同时安装依赖模块:itsdangerous, click, MarkupSafe, Jinja2, Werkzeug

    现在我们来写个简单的登录功能,主要是三个页面:

    • 首页,显示home字样;
    • 登录页,地址/login,有登录表单;
    • 登录后的欢迎页面,如果登录成功,提示欢迎语,否则提示用户名不正确。

    那么一共有3个URL:

    • GET /:首页,返回Home;
    • GET /login:登录页,显示登录表单;
    • POST /login:处理登录表单,显示登录结果。

    user_flask_app.py

    # coding: utf-8
    
    from flask import Flask
    from flask import request
    
    app = Flask(__name__)
    
    # 首页
    @app.route('/', methods=['GET', 'POST'])
    def home():
        return '<h1>Home</h1><p><a href="/login">去登录</a></p>'
    
    # 登录页
    @app.route('/login', methods=['get'])
    def login():
        return '''<form action="/login" method="post">
                  <p>用户名:<input name="username"></p>
                  <p>密码:<input name="password" type="password"></p>
                  <p><button type="submit">登录</button></p>
                  </form>'''
    
    # 登录页处理
    @app.route('/login', methods=['post'])
    def do_login():
        # 从request对象读取表单内容:
        param = request.form
        if(param['username'] == 'yjc' and param['password'] == 'yjc'):
            return '欢迎您 %s !' % param['username']
        else:
            return '用户名或密码不正确。'
        pass
    
    if __name__ == '__main__':
        # run()方法参数可以都为空,使用默认值
        app.run('', 5000)
    

    我们可以打开:http://localhost:5000/ 看效果。实际的Web App应该拿到用户名和口令后,去数据库查询再比对,来判断用户是否能登录成功。

    通过代码我们可以发现,Flask通过Python的装饰器在内部自动地把URL和函数给关联起来。

    注意代码里同一个URL/login分别有GETPOST两种请求,可以映射到两个处理函数中。

    使用模板

    Web框架让我们从编写底层WSGI接口拯救出来了,极大的提高了我们编写程序的效率。

    但代码里嵌套太多的html让整个代码易读性变差,使程序变得复杂。我们需要将后端代码逻辑与前端html分离出来。这就是传说中的MVC:Model-View-Controller,中文名“模型-视图-控制器”。

    Controller负责业务逻辑,比如检查用户名是否存在,取出用户信息等等;

    View负责显示逻辑,通过简单地替换一些变量,View最终输出的就是用户看到的HTML。

    'Model'负责数据的获取,如从数据库查询用户信息等。Model简单可以理解为数据。

    那么就是:Model获取数据,Controlle处理业务逻辑,View显示数据。

    现在,我们把上次直接输出字符串作为HTML的例子用MVC模式改写一下:

    # coding: utf-8
    
    from flask import Flask,request,render_template
    
    app = Flask(__name__)
    
    # 首页
    @app.route('/', methods=['GET', 'POST'])
    def home():
        return render_template('home.html')
    
    # 登录页
    @app.route('/login', methods=['get'])
    def login():
        return render_template('login.html', param = [])
    
    # 登录页处理
    @app.route('/login', methods=['post'])
    def do_login():
        param = request.form
        if(param['username'] == 'yjc' and param['password'] == 'yjc'):
            return render_template('welcome.html', username = param['username'])
        else:
            return render_template('login.html', msg = '用户名或密码不正确。', param = param)
        pass
    
    if __name__ == '__main__':
        app.run('', 5000)
    

    Flask通过render_template()函数来实现模板的渲染。和Web框架类似,Python的模板也有很多种。Flask默认支持的模板是jinja2

    模板页面:
    home.html

    <h1>Home</h1><p><a href="/login">去登录</a></p>
    

    login.html

    {% if msg %}
    <p style="color:red;">{{ msg }}</p>
    {% endif %}
    <form action="/login" method="post">
        <p>用户名:<input name="username" value="{{ param.username }}"></p>
        <p>密码:<input name="password" type="password"></p>
        <p><button type="submit">登录</button></p>
    </form>
    

    welcome.html

    <p>欢迎您, {{ username }} !</p>
    

    项目目录:

    user_flask_app
        |-- templates
            |-- home.html
            |-- login.html
            |-- welcome.html
        |-- user_flask_app.py
    

    render_template()函数第一个参数是模板名,默认是templates目录下。后面的参数是传给模板的变量。变量的值可以是数字、字符串、列表等等。

    在Jinja2模板中,我们用{{ name }}表示一个需要替换的变量。很多时候,还需要循环、条件判断等指令语句,在Jinja2中,用{% ... %}表示指令。

    比如循环输出页码:

    {% for i in page_list %}
        <a href="/page/{{ i }}">{{ i }}</a>
    {% endfor %}
    

    除了Jinja2,常见的模板还有:

    Mako:用<% ... %>和${xxx}的一个模板;
    Cheetah:也是用<% ... %>和${xxx}的一个模板;
    Django:Django是一站式框架,内置一个用{% ... %}和{{ xxx }}的模板。
    
  • 相关阅读:
    Web API 强势入门指南
    毫秒必争,前端网页性能最佳实践
    Windbg Extension NetExt 使用指南 【3】 ---- 挖掘你想要的数据 Managed Heap
    Windbg Extension NetExt 使用指南 【2】 ---- NetExt 的基本命令介绍
    Windbg Extension NetExt 使用指南 【1】 ---- NetExt 介绍
    WCF : 修复 Security settings for this service require Windows Authentication but it is not enabled for the IIS application that hosts this service 问题
    透过WinDBG的视角看String
    Microsoft Azure Web Sites应用与实践【4】—— Microsoft Azure网站的“后门”
    企业IT管理员IE11升级指南【17】—— F12 开发者工具
    WCF : 如何将NetTcpBinding寄宿在IIS7上
  • 原文地址:https://www.cnblogs.com/52fhy/p/6391319.html
Copyright © 2011-2022 走看看