Python学习--20 Web开发

HTTP格式

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

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

HTTP请求格式:前端

GET:python

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

POST:web

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

body data goes here...

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

当遇到\r\n\r\n时,Header部分结束,后面的数据所有是Body。json

HTTP响应格式:flask

200 OK
Header1: Value1
Header2: Value2
Header3: Value3

body data goes here...

HTTP响应若是包含body,也是经过\r\n\r\n来分隔的。后端

请再次注意,Body的数据类型由Content-Type头来肯定,若是是网页,Body就是文本,若是是图片,Body就是图片的二进制数据。浏览器

Body数据是能够被压缩的,若是看到Content-Encoding,说明网站使用了压缩。最多见的压缩方式是gzip。

WSGI接口

了解了HTTP协议的格式后,咱们能够理解一个Web应用的本质:
一、浏览器发送HTTP请求给服务器;
二、服务器接收请求后,生成HTML;
三、服务器把生成的HTML做为HTTP响应的body返回给浏览器;
四、浏览器接收到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 }}的模板。
相关文章
相关标签/搜索