web2
序
前面介绍过网页html的一些技术实现和大体架构,但对于后端boy,还是不太想计较那么多html方面的事,所以想把注意力集中回原先的python体系。针对简单的html的动态生成和一些个底层tcp/udp乃至上层http连接,都不用关注,只需要编写对应的接口响应函数,这种技术叫做WSGI,Web Server Gateway Interface。通常编写样式都是这样:
def application(environ, start_response):
start_response('200 OK', [('content-type', 'text/html')])
return [b'<h1>Hello, wsgi!</h1>']
这么一个接口就编写完毕,但还需要进行调用,另有一个python内置了的WSGI的服务实现,叫wsgiref,可以通过这个库来调用自己编写的WSGI接口,如下:
from wsgiref.simple_server import make_server
# 引入自己编写的接口
from wsgiapp import application
# 实现一个监听8080端口、引用application自定义接口处理的server对象
httpd = make_server('', 8080, application)
print('servint http on port 8080...')
# 持续跑起来
httpd.serve_forever()
和很多程序一样,想要让它停止,就crtl+c(windows),linux就用crtl+z来终止运行。
如上就是简单的WSGI的web app效果,不用自己编写html,它会动态生成html,客户在浏览器访问127.0.0.1:8080/就可以得到服务,当然127.0.0.1就决定了客户只能是本机PC了,哈哈哈。
重写一下application,使其功能丰富点:
def application(environ, start_response):
start_response('200 OK', [('content-type', 'text/html')])
# 读取url中传入信息,再构造响应
body = '<h1>Hello, {}!</h1>'.format(environ['PATH_INFO'][1:] or 'web')
return [body.encode('utf-8')]
上面的框架还是比较的难维护,因为上面还没添加请求方式和不同路由的判断就还是比较简短,所以现在大多数都是用的比较流行的web框架。
一、轻便的flask
flask的使用,和上面的比较相似,都是编写对应的接口处理函数,但在使用形式上还是有差异,毕竟flask作为轻便式实现,提供了许多便利的装饰器,使用前准备:
pip install flask
from flask import Flask
app = Flask(__name__)
@app.route('/')
def sayhello():
return 'Hello, web!'
if __name__ == '__main__':
app.run()
上面flask的代码实现了和最初始的web页面一样的效果,都是简单输出h1标签对应样式的hello,一开始就定义了app对象,其后的路由定义都是在app的route装饰器进行,定义完处理函数后,就run,大致就是这么个流程,如果是命令行跑起来,就需要强行要求脚本命名为app.py,如果想要自定义运行端口、ip等参数,就需要在run函数中传参了。
@app.route('/<name>')
def sayhi(name):
return '<h1>welcome, {}</h1>'.format(name)
嗯,简单实现就这样,再来多点。
1.1 flask的各种接口的实现
一个登录页面的实现
from flask import Flask
from flask import request
app = Flask(__name__)
@app.route('/')
def index():
# 下面的html是一个用户登录页面的实现,form标签的action属性指明了请求url,method指定请求方法
return """<form action="sign" method="post" style="text-align: center;">
<p>用户名:<input name="username"></p>
<p>密码:<input name="password" type="password"></p>
<p><button type="submit">sign in</button></p>
</form>"""
@app.route('/sign', methods=["POST"])
def sign():
# 如果是admin用户且密码为admin就返回登录后页面,否则回应登录失败。
if request.form['username'] == 'admin' and request.form['password'] == 'admin':
return '<h1>Welcome to the first page!</h1>'
return '<h1>Failed in Authentication</h1>'
if __name__ == '__main__':
app.run()
效果如下:
一个图片上传实现
from flask import jsonify
from flask import redirect
import os
app.config['UPLOAD_FOLDER'] = 'upload'
@app.route('/')
@app.route('/home')
def home():
return """<form action="upload" enctype="multipart/form-data" method="post">
<input type="file" name="fiiii">
<input type="submit" value="上传">
</form>"""
@app.route('/upload', methods=["POST"])
def upload():
if not os.path.exists('upload'):
os.makedirs('upload')
f = request.files['fiiii']
print(app.config['UPLOAD_FOLDER'], f.filename)
upload_path = os.path.join('upload', f.filename)
f.save(upload_path)
if os.path.exists(upload_path):
return jsonify({'errno':0, 'msg':'上传成功'})
else:
return jsonify({'errno':110, 'errmsg':'上传失败'})
关于save接口,最好拼接好保存文件名再传给save参数,不然容易出现permission error,另外就是jsonify返回响应信息中文乱码的问题,有介绍app.config['JSON_AS_ASCII'] = False
可以,但不管用。加点东西,上传图片成功后就显示图片:
@app.route('/upload', methods=["POST"])
def upload():
if not os.path.exists('upload'):
os.makedirs('upload')
f = request.files['fiiii']
print(app.config['UPLOAD_FOLDER'], f.filename)
upload_path = os.path.join('upload', f.filename)
f.save(upload_path)
if os.path.exists(upload_path):
return redirect('show/{}'.format(upload_path))
else:
back_msg = {'errno':110, 'errmsg':'上传失败'}
return jsonify(back_msg)
@app.route('/show/<name>')
def display(name):
html = '<img src="{}" alt="{}" height="400", width="auto">'.format(name, name.split('\\')[-1].split('.')[0])
print(html)
return html
但在上传成功后返回图片页面,但直接构造html img的办法好像不管用,还是要手动来构造响应,或者使用template,返回构造的html模板页面。在项目中创建templates文件夹并放置下面index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>display</title>
</head>
<body>
<img src="{{path}}", alt="{{alt_name}}", height="400", width="auto"/>
</body>
</html>
接口的return语句修改一下
from flask import render_template
@app.route('/upload', methods=["POST"])
def upload():
if not os.path.exists('upload'):
os.makedirs('upload')
f = request.files['fiiii']
upload_path = os.path.join('upload', f.filename)
f.save(upload_path)
if os.path.exists(upload_path):
return render_template('index.html', path=upload_path, alt_name=f.filename.split('.')[0])
else:
back_msg = {'errno':110, 'errmsg':'上传失败'}
return jsonify(back_msg)
不过这样也不行,总是在get请求图片的时候失败,不知道为什么,单独构造html的时候也没问题啊,难受,只能用构造response的方法了。
@app.route('/upload', methods=["POST"])
def upload():
if not os.path.exists('upload'):
os.makedirs('upload')
f = request.files['fiiii']
upload_path = os.path.join('upload', f.filename)
f.save(upload_path)
if os.path.exists(upload_path):
with open(upload_path, 'rb') as f:
response = make_response(f.read())
response.headers['content-type'] = 'image/png'
return response
else:
back_msg = {'errno':110, 'errmsg':'上传失败'}
return jsonify(back_msg)
百度了不少,对口答案太少了,后面根据日志输出确定了图片的获取是会另外请求,而没有编写这个路由就导致了图片的html加载不完全:
@app.route('/upload', methods=["POST"])
def upload():
f = request.files['fiiii']
name = f.filename
upload_path = os.path.join('upload', name)
f.save(upload_path)
if os.path.exists(upload_path):
return render_template('index.html',
path=name,
alt_name=name.split('.')[0])
else:
back_msg = {'errno':110, 'errmsg':'上传失败'}
return jsonify(back_msg)
# 错开一点,防止同名路由访问,顺带还需要修改一下index.html
@app.route('/img/<name>')
def send_file(name):
with open('upload/'+name, 'rb') as f:
response = make_response(f.read())
response.headers['content-type'] = 'image/png'
return response
因为index.html中设定img的src来源为upload_path,也即是upload/pic路径,所以要针对性写一个这样的url路由,这就是send_file的来源。
本地测试html总让我有种错觉就是,html和内部的img是一体的,这个习惯要改一改。功能能跑了,完善一下,只允许图片的上传,并且根据jinja2语法加个判断,因为gif和普通图片的img属性略有不同,再修改一下样式使得它居中:
from flask import Flask
from flask import request
from flask import jsonify
from flask import render_template
from flask import make_response
import os
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'upload'
app.config['JSON_AS_ASCII'] = False
ALLOW_PICS = ['png', 'jpeg', 'jpg', 'PNG', 'JPG', 'JPEG', 'gif', 'GIF']
@app.route('/sign')
def sign():
# 下面的html是一个用户登录页面的实现,form标签的action属性指明了请求url,method指定请求方法
return """<form action="sign" method="post" style="text-align: center;">
<p>用户名:<input name="username"></p>
<p>密码:<input name="password" type="password"></p>
<p><button type="submit">sign in</button></p>
</form>"""
@app.route('/sign', methods=["POST"])
def signin():
# 如果是admin用户且密码为admin就返回登录后页面,否则回应登录失败。
if request.form['username'] == 'admin' and request.form['password'] == 'admin':
return '<h1>Welcome to the first page!</h1>'
return '<h1>Failed in Authentication</h1>'
@app.route('/')
@app.route('/home')
def home():
return """<form action="upload" enctype="multipart/form-data" method="post">
<input type="file" name="fiiii">
<input type="submit" value="上传">
</form>"""
@app.route('/upload', methods=["POST"])
def upload():
f = request.files['fiiii']
name = f.filename
upload_path = os.path.join('upload', name)
if f and name.split('.')[-1] in ALLOW_PICS:
if 'gif' or 'GIF' in name:
is_gif = True
else:
is_gif = False
f.save(upload_path)
if os.path.exists(upload_path):
return render_template('index.html',
is_gif = is_gif,
path=upload_path,
name=name.split('.')[0])
else:
jsonify({'errno':1101, 'errmsg':'failed in saving'})
else:
return jsonify({'errno':110, 'errmsg':'failed to upload, check the format'})
@app.route('/upload/<name>')
def send_file(name):
with open('upload/'+name, 'rb') as f:
response = make_response(f.read())
response.headers['content-type'] = 'image/png'
return response
if __name__ == '__main__':
app.run()
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>display</title>
</head>
<body>
{% if is_gif %}
<div style="text-align:center;"><img src="{{path}}", alt="{{name}}", height="400", width="auto" loop="true" autoplay="true"/><p>{{name}}</p></div>
{% else %}
<div style="text-align:center"><img src="{{path}}", alt="{{name}}", height="400", width="auto"/><p>{{name}}</p></div>
{% endif %}
<br/>
<form action="upload" enctype="multipart/form-data" method="post" align="center">
<input type="file" name="fiiii">
<input type="submit" value="上传">
</form>
</body>
</html>
好了,效果上就是每次上传图片成功都会展示,并且还可以继续上传图片。
1.2 摸一摸jinja2语法
首要的简单语法:
- {{ ... }},标记html中的变量,和语言沟通的变量实体;
- {% ... %},标记语句,各种逻辑语句诸如if、for等,都用这个,标记了开头需要标记结束;
- {# ... #},用来写注释语句。
简单的使用语句:
变量
<body>
<!--name变量,在python脚本中就直接在render_template中传入指定参数即可-->
<p>hello, {{name}}</p>
</body>
分支
<body>
{% if is_or_not %}
<p>is_or_not为true</p>
{% else %}
<p>is_or_not为false</p>
{% endif %}
</body>
<body>
{% for comment in comments %}
<li>{{comment.id, comment.name}}</li>
{% endfor %}
</body>
注释就自然不用多言了,很明了了。继续展开,针对变量,不同类型的变量,存储的信息也不同,比如针对python的各种标准对象诸如字典、列表等等,在python里面怎么用,在这里就怎么用。除此以外,jinja2的特色就是针对变量有着过滤器的功能,形式就是{{变量|过滤器}}
,具体语义如下:
过滤器名 | 说明 |
---|---|
safe | 不转义 |
capitalize | 变量首字母大写,其余小写 |
lower | 变量小写 |
upper | 变量大写 |
title | 变量中每个单词首字母大写 |
trim | 变量首尾空格去除 |
striptags | 去除变量中的html标签 |
它的样式是变量后加上管道符|然后接api,就有点像过滤的样子才叫过滤器吧,更多的看官网吧。
模板继承
和面向对象的特性那样,jinja语法提供模板的继承,在应用当中,可以提供一个base模板,这个模板只提供关于UI界面的简单界定划分,后续的内容填充,都交给子模板。看了jinja中文网的例子,大致是这样:
给定一个base,
<!DOCTYPE html>
<html>
<head>
{% block head %}
<link rel="stylesheet" href="style.css" />
<title>{% block title %}{% endblock %} - My Webpage</title>
{% endblock %}
</head>
<body>
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
© Copyright 2008 by <a href="http://domain.invalid/">you</a>.
{% endblock %}
</div>
</body>
block和endblock包起来的区域就是可操作区域,对于我来说就是相当于另类的虚函数,可以被子模板重写。针对base的子模板扩展:
{% extends "base.html" %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
<style type="text/css">
.important { color: #336699; }
</style>
{% endblock %}
{% block content %}
<h1>Index</h1>
<p class="important">
Welcome on my awesome homepage.
</p>
{% endblock %}
上面的extends表明继承于某某,super就有点类似python了,一样的意思,然后用上和base相同的block,再选择性重写里面的内容就ok了。因为引入了block的使用,它也支持嵌套概念,所以为了可读性的需求,运行在endblock的时候添加命名。
针对循环
在for循环中,有着方便查询当前迭代在循环中的位置的接口:
变量 | 描述 |
---|---|
loop.index | 当前循环迭代的次数,从1开始 |
loop.indexo | 从0开始计数的当前循环迭代次数 |
loop.revindex | 到循环结束还需要迭代的次数,从1开始 |
loop.revindexo | 到循环结束还需要迭代的次数,从0开始 |
loop.first | 判断是否是第一次迭代 |
loop.last | 判断是否是最后一次迭代 |
loop.length | 循环需要迭代的次数 |
除此以外,还有一些辅助性的特殊函数,比如range、cycler等,这种接口可以查看中文网。另外也还有更多的可用功能,但这里不想详述太多,烦人,要查的时候再看中文网吧。
后面的内容比较多,发布在了个人网站上,可以移步到那里:web2